Skip to content
Docs

Command palette

Jump to a page or search the docs.

Changelog

Social sign-in setup

The Google and Apple credentials to create, their return URLs, the Worker secrets and the mobile side of Continue with Apple and Continue with Google.

Markdown

How it works

Continue with Apple and Continue with Google are built on web (/login, /signup) and in the app (P-Entrar, sign-up), and stay hidden until the server has credentials. appConfig.get lists a provider in socialProviders only when its id and secret are set (social-providers.ts). Without any credentials nothing changes on either surface.

SurfaceFlow
WebThe browser leaves for the provider, returns to https://<origin>/api/auth/callback/<provider>, then goes to the page that sent it to /login (?redirect=) or to /dashboard. A refusal returns to the same page with ?error=; the page explains account_not_linked and offers a retry for anything else. A cancel shows nothing.
AppBetter Auth's Expo plugin opens the same server flow in a system auth session (ASWebAuthenticationSession on iOS, a Custom Tab on Android). The callback returns to the app scheme (autofolio:///, autofolio-dev:/// in the dev build) with the session, and useEnterApp lands on the garage. A refusal also returns to the scheme, so the sheet closes; the app shows no message for it (see below).

On iOS Google shows only beside Apple (App Store guideline 4.8) and Apple shows only on iOS (auth.social.ts); the web offers both.

Account linking. The first sign-in with a new email creates the account and its Organization. An email that already has an account is joined only when the provider vouches for the email and the account's own email is verified (account.accountLinking with requireLocalEmailVerified, no trusted providers). Anything else answers account_not_linked, so a password sign-up squatting someone's address is never joined. Apple's Hide My Email addresses are new accounts. Linked accounts are managed from the app's Security → Apple and Google. social-sign-in.test.ts proves the redirect start, the app-scheme return, the refused foreign callbackURL and the three linking cases.

Google

In Google Cloud console for the AutoFolio project:

  1. OAuth consent screen (Google Auth Platform → Branding/Audience): user type External, app name AutoFolio, support email, authorized domain its-live.dev, home page https://autofolio.its-live.dev, privacy policy https://autofolio.its-live.dev/privacy, terms https://autofolio.its-live.dev/terms. Scopes: openid, email, profile only (no verification review needed). Publish the app to In production, or only test users can sign in.

  2. One OAuth client, type Web application (Clients → Create client):

    • Authorized JavaScript origins: https://autofolio.its-live.dev, http://localhost:3005
    • Authorized redirect URIs: https://autofolio.its-live.dev/api/auth/callback/google, http://localhost:3005/api/auth/callback/google

    Its client id and secret become GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET.

Apple

In Apple Developer → Certificates, Identifiers & Profiles, team of the App Store account (its Team ID is at the top right):

  1. App ID io.zietec.autofolio: enable the Sign in with Apple capability, as a primary App ID.

  2. Services ID (Identifiers → + → Services IDs), e.g. io.zietec.autofolio.signin. Enable Sign in with Apple → Configure: primary App ID io.zietec.autofolio; Domains and Subdomains autofolio.its-live.dev; Return URLs https://autofolio.its-live.dev/api/auth/callback/apple. Apple refuses localhost and plain http: test Apple on the production origin or an HTTPS tunnel registered here too. The Services ID becomes APPLE_CLIENT_ID.

  3. Key (Keys → +): name it, enable Sign in with Apple, Configure → primary App ID io.zietec.autofolio. Download AuthKey_<KEY_ID>.p8 (Apple lets you download it once; keep it in the password manager, never in the repo) and note the Key ID.

  4. Client secret. Apple takes a JWT signed with that key as the client secret, valid for at most six months. Mint it with scripts/apple-client-secret.mjs (180 days) and pipe it straight into the Worker secret, so it never lands in a file or the terminal history:

    node scripts/apple-client-secret.mjs --team-id <TEAM_ID> --key-id <KEY_ID> \
      --client-id io.zietec.autofolio.signin --key ~/secure/AuthKey_<KEY_ID>.p8 \
      | pnpm --filter web exec wrangler secret put APPLE_CLIENT_SECRET --name autofolio-web

    Re-run it before the 180 days run out (set a reminder); an expired secret makes every Apple sign-in fail with invalid_client.

  5. Email relay (Services → Sign in with Apple for Email Communication → Configure): register the sending domain its-live.dev and the EMAIL_FROM address noreply@its-live.dev. Without it, mail to a Hide My Email address (…@privaterelay.appleid.com) bounces: verification, sign-in alerts and reminders never arrive.

Worker secrets

The values go to the autofolio-web Worker as secrets. Run from the repository root; each command prompts for the value (or reads it from a pipe, as above). wrangler secret put deploys a new version at once, so the buttons appear as soon as both values of a provider are set.

NameValue
GOOGLE_CLIENT_IDThe web client's id (….apps.googleusercontent.com)
GOOGLE_CLIENT_SECRETThe web client's secret
APPLE_CLIENT_IDThe Services ID (io.zietec.autofolio.signin)
APPLE_CLIENT_SECRETThe JWT from scripts/apple-client-secret.mjs
APPLE_APP_BUNDLE_IDENTIFIERio.zietec.autofolio, the audience of a native Apple ID token (not secret)
pnpm --filter web exec wrangler secret put GOOGLE_CLIENT_ID --name autofolio-web
pnpm --filter web exec wrangler secret put GOOGLE_CLIENT_SECRET --name autofolio-web
pnpm --filter web exec wrangler secret put APPLE_CLIENT_ID --name autofolio-web
pnpm --filter web exec wrangler secret put APPLE_APP_BUNDLE_IDENTIFIER --name autofolio-web
# APPLE_CLIENT_SECRET: the pipe in the Apple steps above.

BETTER_AUTH_URL (https://autofolio.its-live.dev) and NATIVE_APP_SCHEME (autofolio) are already plain vars in apps/web/wrangler.jsonc; the callback URLs above derive from the first, the trusted app scheme from the second. Pull request previews get none of these secrets, so they show no social buttons (their origins are not registered with Google or Apple anyway).

Locally, add GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET to apps/web/.env (commented in .env.example) to try Google on http://localhost:3005.

Mobile (EAS)

Nothing to add. The app reads no Apple or Google value: it calls the server at EXPO_PUBLIC_SERVER_URL, already https://autofolio.its-live.dev in the production profile of apps/mobile/eas.json, and asks appConfig.get which providers to show. No EAS environment variable, secret or new binary is needed; the buttons appear in installed builds as soon as the Worker has the secrets. The browser flow needs no Sign in with Apple entitlement in the app.

Check it

curl -s -X POST https://autofolio.its-live.dev/api/rpc/appConfig/get \
  -H 'content-type: application/json' -d '{"json":{"platform":"web"}}'

socialProviders lists apple and google; /login shows both buttons above the email form, and each opens the provider's consent page. Then sign in once per provider on the web and in the app.

Known limits

  • Native Sign in with Apple. On iOS, Apple runs in the system auth session, not the native Apple sheet. If App Review asks for the native sheet, add expo-apple-authentication (new binary, usesAppleSignIn), pass its ID token as authClient.signIn.social({ provider: "apple", idToken: { token } }); the server already accepts it through APPLE_APP_BUNDLE_IDENTIFIER.
  • Refusals in the app. The Expo client drops the URL the auth session returns, so the app cannot tell a refusal (account_not_linked) from a cancel and shows nothing; the web explains it.
Edit on GitHubDocumentation is in English.

We use essential cookies to make our app work. With your consent, we also use optional cookies to improve your experience. Read our Cookie Policy and Privacy Policy.