diff --git a/apps/swift/Resources/PackRat-iOS.entitlements b/apps/swift/Resources/PackRat-iOS.entitlements index 0c67376eba..a812db506f 100644 --- a/apps/swift/Resources/PackRat-iOS.entitlements +++ b/apps/swift/Resources/PackRat-iOS.entitlements @@ -1,5 +1,10 @@ - + + com.apple.developer.applesignin + + Default + + diff --git a/packages/api/.dev.vars.e2e.example b/packages/api/.dev.vars.e2e.example index 72f0971454..4b263a8f2a 100644 --- a/packages/api/.dev.vars.e2e.example +++ b/packages/api/.dev.vars.e2e.example @@ -58,6 +58,7 @@ PACKRAT_GUIDES_BASE_URL=https://guides.packratai.com/ EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID= EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID= GOOGLE_CLIENT_ID= +GOOGLE_IOS_CLIENT_ID= GOOGLE_CLIENT_SECRET= # ── Maps ───────────────────────────────────────────────────────────────────── @@ -68,6 +69,7 @@ SENTRY_DSN= # ── Apple Sign In ───────────────────────────────────────────────────────────── APPLE_CLIENT_ID= +APPLE_SWIFT_CLIENT_ID= APPLE_PRIVATE_KEY= APPLE_KEY_ID= APPLE_TEAM_ID= diff --git a/packages/api/src/auth/index.ts b/packages/api/src/auth/index.ts index 4242b46c8a..8705b71196 100644 --- a/packages/api/src/auth/index.ts +++ b/packages/api/src/auth/index.ts @@ -193,7 +193,11 @@ async function buildAuth(env: ValidatedEnv): Promise { socialProviders: { google: { - clientId: env.GOOGLE_CLIENT_ID ?? '', + // iOS native Google Sign-In requests an id token whose `aud` claim is the + // iOS OAuth client ID, distinct from the web client ID — accept both audiences. + clientId: [env.GOOGLE_CLIENT_ID, env.GOOGLE_IOS_CLIENT_ID].filter((id): id is string => + Boolean(id), + ), clientSecret: env.GOOGLE_CLIENT_SECRET ?? '', }, // Always register Apple when clientId is present so the native id-token @@ -201,7 +205,9 @@ async function buildAuth(env: ValidatedEnv): Promise { // verifies against Apple's public JWKS; the secret is only used for the // web OAuth redirect flow). // audience covers all EAS build variants — Apple puts the bundle ID in - // the `aud` claim, which differs per variant (.dev, .preview, base). + // the `aud` claim, which differs per variant (.dev, .preview, base) — plus + // the native Swift app's distinct bundle ID, which is a separate Xcode + // target and not an EAS build variant of the Expo app. ...(env.APPLE_CLIENT_ID ? { apple: { @@ -212,6 +218,7 @@ async function buildAuth(env: ValidatedEnv): Promise { env.APPLE_CLIENT_ID, `${env.APPLE_CLIENT_ID}.dev`, `${env.APPLE_CLIENT_ID}.preview`, + ...(env.APPLE_SWIFT_CLIENT_ID ? [env.APPLE_SWIFT_CLIENT_ID] : []), ], }, } diff --git a/packages/api/src/utils/env-validation.ts b/packages/api/src/utils/env-validation.ts index 7d0ab34d59..3597e42e7d 100644 --- a/packages/api/src/utils/env-validation.ts +++ b/packages/api/src/utils/env-validation.ts @@ -48,9 +48,15 @@ export const apiEnvObjectSchema = z.object({ E2E_TEST_USER_ID: z.string().uuid().optional(), // Google OAuth (Better Auth social provider) GOOGLE_CLIENT_ID: z.string(), + // iOS native Google Sign-In uses a separate OAuth client whose ID appears in the + // ID token's `aud` claim — Better Auth must accept both audiences or native sign-in fails. + GOOGLE_IOS_CLIENT_ID: z.string().optional(), GOOGLE_CLIENT_SECRET: z.string(), // Apple Sign In (Better Auth social provider) APPLE_CLIENT_ID: z.string(), // bundle ID e.g. world.packrat.app + // Native Swift app's distinct bundle ID (separate Xcode target, not an EAS build + // variant of the Expo app) — appears in its Apple ID token's `aud` claim. + APPLE_SWIFT_CLIENT_ID: z.string().optional(), APPLE_PRIVATE_KEY: z.string(), // .p8 key contents — store via wrangler secret APPLE_KEY_ID: z.string(), APPLE_TEAM_ID: z.string(),