Skip to content
supastack
Browse documentation
WebNext.js

For AI agents: fetch the complete documentation index at /llms.txt, the full documentation corpus at /llms-full.txt, the agent skill at /skill.md, or this page as Markdown at /docs/web/apple-sign-in.md (or send Accept: text/markdown).

Add Apple sign-in

Register an App ID, a Services ID, and a Sign in with Apple key in the Apple Developer portal, then wire the client pair into the app.

Beginner walkthrough

Prerequisites: the web app running locally (Ship in 5 minutes), a migrated database for real accounts (Set up database), and an Apple Developer Program membership — the same $99-per-year account the mobile accounts guide enrolls, so complete that step before continuing here if you have not already. Everything inside the developer portal below costs nothing extra.

Why it matters: App Store review requires Sign in with Apple whenever an iOS app offers third-party social sign-in such as Google or GitHub, and it is a solid low-friction button on the web too — Apple users sign in with the account already on their devices.

Quick terms: an App ID is your app's registered identifier (com.yourcompany.yourapp). A Services ID is a second identifier Apple issues for web and server sign-in — that one becomes your client ID. A return URL is the exact address Apple may send a signed-in user back to — the kit's Better Auth callback. A secret key is the .p8 file Apple gives you once; you turn it into the client secret yourself.

1. Register an App ID with the Sign in with Apple capability. 2. Register a Services ID that points at it and holds your domain plus return URL. 3. Create a Sign in with Apple key and download the .p8 file. 4. Turn the key into a client-secret JWT and set the three env values.

One honest limitation: Apple's return URLs must be absolute https:// addresses on a registered domain — localhost and plain http are rejected, so the button cannot be fully exercised on a bare local server. Verify on your deployed URL, or expose the local server through an https tunnel (for example cloudflared tunnel or ngrok) and register the tunnel domain plus its callback while testing.

Register the App ID

1. Open developer.apple.com/account and sign in with the account holding the Developer Program membership.

2. Open Certificates, Identifiers & Profiles → Identifiers and click the blue + button.

3. Select App IDs → Continue, keep the type set to App → Continue.

4. Description: your product name. Bundle ID: choose Explicit and enter the reverse-domain form com.yourcompany.yourapp — if you ship the iOS app, use exactly the ios.bundleIdentifier in apps/mobile/app.json so both platforms share one App ID.

5. In the capability list tick Sign in with Apple → Continue → Register.

Expected: the identifier appears in the Identifiers list with the Sign in with Apple capability enabled. Reuse this one App ID — the Services ID and the key both attach to it as the primary app.

Register the Services ID and return URL

1. Back in Identifiers click + again, choose Services IDs → Continue.

2. Description: something like yourapp web sign-in. Identifier: another reverse-domain name — com.yourcompany.yourapp.web is the convention — deliberately different from the App ID. This Services ID is the client ID the server uses for web sign-in; copy it exactly, then Continue → Register.

3. Select the new Services ID in the list, tick Sign in with Apple, and click Configure.

4. In the dialog set Primary App ID to the App ID from the previous section. Under Website URLs add your domain (for example yourdomain.com), and under Return URLs add https://yourdomain.com/api/auth/callback/apple — that path is the kit's Better Auth callback, {APP_URL}/api/auth/callback/apple. Save → Continue → Save.

Each return URL must be an absolute https:// address on a registered domain: localhost, bare IP addresses, and http are rejected. For local development run the app behind an https tunnel, add the tunnel domain to the Services ID, register https://<tunnel-domain>/api/auth/callback/apple, and set APP_URL to the tunnel URL while testing.

Expected: the Services ID reads Sign in with Apple → Enabled. If the portal rejects your domain, finish domain ownership verification on the account page first.

Create the Sign in with Apple key

1. Open Keys in the left sidebar → +. Name the key (for example yourapp sign-in key), tick Sign in with Apple, click Configure, choose the App ID as the Primary App ID → Save → Continue → Register.

2. On the confirmation screen record the Key ID, then click Download — the .p8 file is offered exactly once. If you lose it, revoke the key and create another; Apple does not keep a copy.

3. Note your Team ID too — it is printed in the top-right corner of the developer account pages, next to your account name.

Expected: you now hold three values — the .p8 file, a Key ID, and your Team ID. Keep the .p8 file out of Git: it signs as your developer account.

Add the values to the app

The kit expects exactly the three lines below — the same clientId/clientSecret pair every social provider uses. APPLE_CLIENT_ID is the Services ID (com.yourcompany.yourapp.web), not the App ID.

Apple issues no static client secret, so APPLE_CLIENT_SECRET is a JWT you sign yourself from the Team ID, the Key ID, and the .p8 private key — the Better Auth documentation ships the short generator script for it. A signed secret stays valid at most six months: set a reminder to regenerate it and update the env value, because sign-in fails the moment it lapses. Never paste the .p8 file's contents or the raw Key ID into env — only the generated JWT belongs there.

The client secret and the .p8 file are server-only: never in NEXT_PUBLIC_/EXPO_PUBLIC_ variables and never committed — in production set them through your host's secret store. Restart the dev server after editing; env changes are read at startup.

# apps/web/.env.local
AUTH_APPLE_ENABLED=true
APPLE_CLIENT_ID=com.yourcompany.yourapp.web   # the Services ID, not the App ID
APPLE_CLIENT_SECRET=<JWT signed with Team ID + Key ID + .p8>

Verify and troubleshoot

Open your APP_URL — a deployed URL or the https tunnel — at /auth, click the Apple button, sign in with any Apple ID (it does not need to be the developer account), and confirm you return signed in.

Most failures are one of three mix-ups: Services ID vs App ID — the client ID must be the Services ID for web sign-in; a return-URL mismatch — the callback must equal a registered Return URL exactly, protocol, domain and path included; or an expired/invalid client secret — regenerate the JWT, which is only a signed token, not the .p8 contents.

Until AUTH_APPLE_ENABLED=true is set, the Apple option on /auth stays disabled — check the env names and restart. Enabling the flag without both APPLE_CLIENT_ID and APPLE_CLIENT_SECRET fails startup and names the missing variable.

The iOS app reaches this same backend — ship Sign in with Apple inside it so App Store review accepts the other social buttons. The mobile accounts guide explains what review looks for.

If you want to continue setup, read Add Stripe payments.

Frequently asked questions

14 questions

What is supastack?

supastack is a modular foundation for building AI-powered SaaS products. It brings together common infrastructure so you can focus on product-specific features.

Does it support web and mobile?

Yes. supastack includes Next.js and Expo apps as starting points for web and mobile experiences.

What authentication is included?

Better Auth covers email OTP, magic-link, and social sign-in; email/password stays available at the API layer and through the Auth.js credentials engine for products that need it. Personal workspaces are provisioned with the account so product records stay scoped from day one.

How does it support AI?

supastack includes configurable multi-provider AI, usage tracking, budgets, and encrypted bring-your-own-key support.

How are subscriptions handled?

For products built on the kit, Stripe billing, subscriptions, and entitlements form the paid-access foundation. Provider secrets stay server-only.

Where does my product-specific code live?

Product-specific features remain in a separate codebase while the product configures and integrates supastack’s foundation modules.

How does supastack pricing work?

The kit is a one-time purchase with lifetime updates: One Platform at $249 (temporary launch price, $100 off the regular $349) — your pick of Next.js (Web), Expo (iOS & Android), or Tauri (macOS & Windows Desktop) — or the All Platforms Bundle at $499 (temporary launch price, $100 off the regular $599) with every platform and the full monorepo, plus $75 per extra developer seat. No kit subscription. Checkout on this site delivers your license key by email, with repository access via GitHub invite.

How do workspaces scope my data?

User-owned records are scoped by workspaceId. Identity, entitlements, AI usage, billing events, and audit entries stay inside the personal workspace boundary.

How do background jobs work?

Trigger.dev is the explicit job runtime. The JobRunner contract only promises enqueue, status, and cancel—workflow semantics stay Trigger.dev-specific and explicit.

How is email different from outreach?

Transactional email covers product workflows such as completion notices. Cold outreach and campaigns are product concerns outside this kit’s email module.

How do mobile purchases work?

Mobile includes native purchases and restore through RevenueCat, with access verified by your server. Configure the store accounts and complete device testing before release. Web billing remains separate.

Where do AI provider keys live?

Bring-your-own-key credentials are encrypted and stay server-only. Keys are redacted from logs; usage and budgets remain visible at the workspace level.

Which payment methods does checkout accept?

The Stripe-hosted checkout accepts cards and can also offer Alipay and WeChat Pay once those wallets are enabled on the Stripe account — handy for buyers in China. Wallet authorizations confirm asynchronously, so the license page updates as soon as the payment clears.

Can I get a refund?

After you've got access to the repository, supastack is yours forever, so it cannot be refunded. But rest assured, you get the complete source code with lifetime updates to build and ship unlimited products for yourself or clients. You just cannot resell the code or redistribute the repository as a starter kit or template.

EN
Theme