[GH-ISSUE #7550] Apple sign-in "Invalid id token" #28161

Open
opened 2026-04-17 19:34:23 -05:00 by GiteaMirror · 9 comments
Owner

Originally created by @Alvi24 on GitHub (Jan 22, 2026).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/7550

Originally assigned to: @Paola3stefania on GitHub.

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Setup apple with correct credentials in the backend
  2. Call this on client:
import * as AppleAuthentication from "expo-apple-authentication";

 const credential = await AppleAuthentication.signInAsync({
        requestedScopes: [
          AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
          AppleAuthentication.AppleAuthenticationScope.EMAIL,
        ],
      });

      if (!credential.identityToken) {
        throw new Error("Failed to get Apple identity token");
      }

      return await authClient.signIn.social({
        provider: "apple",
        idToken: {
          token: credential.identityToken,
          // nonce: credential.authorizationCode ?? undefined,
          // accessToken: credential.identityToken,
        },
        callbackURL: "/onboard",
      });

Current vs. Expected behavior

i want to add native apple signIn in my expo app.
i have authenticate using expo-apple-authentication and than pass the identityToken provided to the betterAuth authClient
but this causes an error in backend:

[CONVEX H(POST /api/auth/sign-in/social)] [ERROR] ERROR [Better Auth]: Invalid id token' {
  provider: 'apple'
}

NOTE: when not providing the idToken object and signing in through web than the authentication is working correctly( meaning that the configuration is done correctly)

What version of Better Auth are you using?

1.4.17

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.2.0: Tue Nov 18 21:09:45 PST 2025; root:xnu-12377.61.12~1/RELEASE_ARM64_T6030",
    "release": "25.2.0",
    "cpuCount": 11,
    "cpuModel": "Apple M3 Pro",
    "totalMemory": "18.00 GB",
    "freeMemory": "0.57 GB"
  },
  "node": {
    "version": "v24.11.0",
    "env": "development"
  },
  "packageManager": {
    "name": "bun",
    "version": "1.3.6"
  },
  "frameworks": null,
  "databases": null,
  "betterAuth": {
    "version": "Unknown",
    "config": null
  }
}

Which area(s) are affected? (Select all that apply)

Client, Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
  trustedOrigins: [
      "[appScheme]://",
      "https://appleid.apple.com",
    ],
account: {
      accountLinking: {
        enabled: true,
      },
    },
   apple: {
        clientId: process.env.APPLE_CLIENT_ID,
        clientSecret: process.env.APPLE_CLIENT_SECRET,
        appBundleIdentifier: process.env.APPLE_APP_BUNDLE_IDENTIFIER,
        // appBundleIdentifier: requireEnv("APPLE_APP_BUNDLE_IDENTIFIER"),
        // mapProfileToUser: ({}) => {},
        scope: ["name", "email"],
      },
});

Additional context

No response

Originally created by @Alvi24 on GitHub (Jan 22, 2026). Original GitHub issue: https://github.com/better-auth/better-auth/issues/7550 Originally assigned to: @Paola3stefania on GitHub. ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Setup apple with correct credentials in the backend 2. Call this on client: ``` import * as AppleAuthentication from "expo-apple-authentication"; const credential = await AppleAuthentication.signInAsync({ requestedScopes: [ AppleAuthentication.AppleAuthenticationScope.FULL_NAME, AppleAuthentication.AppleAuthenticationScope.EMAIL, ], }); if (!credential.identityToken) { throw new Error("Failed to get Apple identity token"); } return await authClient.signIn.social({ provider: "apple", idToken: { token: credential.identityToken, // nonce: credential.authorizationCode ?? undefined, // accessToken: credential.identityToken, }, callbackURL: "/onboard", }); ``` ### Current vs. Expected behavior i want to add native apple signIn in my expo app. i have authenticate using expo-apple-authentication and than pass the identityToken provided to the betterAuth authClient but this causes an error in backend: ``` [CONVEX H(POST /api/auth/sign-in/social)] [ERROR] ERROR [Better Auth]: Invalid id token' { provider: 'apple' } ``` NOTE: when not providing the idToken object and signing in through web than the authentication is working correctly( meaning that the configuration is done correctly) ### What version of Better Auth are you using? 1.4.17 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.2.0: Tue Nov 18 21:09:45 PST 2025; root:xnu-12377.61.12~1/RELEASE_ARM64_T6030", "release": "25.2.0", "cpuCount": 11, "cpuModel": "Apple M3 Pro", "totalMemory": "18.00 GB", "freeMemory": "0.57 GB" }, "node": { "version": "v24.11.0", "env": "development" }, "packageManager": { "name": "bun", "version": "1.3.6" }, "frameworks": null, "databases": null, "betterAuth": { "version": "Unknown", "config": null } } ``` ### Which area(s) are affected? (Select all that apply) Client, Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ trustedOrigins: [ "[appScheme]://", "https://appleid.apple.com", ], account: { accountLinking: { enabled: true, }, }, apple: { clientId: process.env.APPLE_CLIENT_ID, clientSecret: process.env.APPLE_CLIENT_SECRET, appBundleIdentifier: process.env.APPLE_APP_BUNDLE_IDENTIFIER, // appBundleIdentifier: requireEnv("APPLE_APP_BUNDLE_IDENTIFIER"), // mapProfileToUser: ({}) => {}, scope: ["name", "email"], }, }); ``` ### Additional context _No response_
GiteaMirror added the platformneeds: infobugoauth labels 2026-04-17 19:34:23 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Jan 22, 2026):

It seems you're encountering an "Invalid id token" error when trying to implement Apple Sign-In with Better Auth. This can often happen due to misconfiguration with the Apple authentication credentials.

Here are some steps you can take to troubleshoot this issue:

  1. Correct Client ID: Make sure you're using the correct client ID. For native iOS implementations, the client ID should be your app's bundle ID, not the service ID used in web apps. This difference can lead to the "Invalid id token" error if the wrong ID is used.

  2. Client Secret Generation: Ensure your client secret is correctly generated. Apple requires a JSON Web Token (JWT) that should be dynamically generated using the .p8 key file. Refer to the Apple Developer documentation for generating this secret, and remember that it should be included in the backend configuration, typically set as clientSecret [1].

  3. Trusted Origins: Confirm that https://appleid.apple.com is included in the trustedOrigins array in your Better Auth configuration. This is crucial for allowing communication with Apple's servers [2].

  4. Debugging Information: Double-check the token you are getting from Apple's sign-in request. Use tools to decode the JWT and ensure that its claims (like aud and iss) are correct and match your app’s setup.

If you have gone through these steps and the issue persists, I recommend contacting the Better Auth team for more tailored assistance. You can tag either @bekacru or @ping__ for help!

Have you tried any of these troubleshooting steps already? Let me know how it goes, or if you have more questions!

If you need more help, tag @better-auth-agent in a comment so I can respond.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3783847612 --> @better-auth-agent[bot] commented on GitHub (Jan 22, 2026): It seems you're encountering an "Invalid id token" error when trying to implement Apple Sign-In with Better Auth. This can often happen due to misconfiguration with the Apple authentication credentials. Here are some steps you can take to troubleshoot this issue: 1. **Correct Client ID**: Make sure you're using the correct client ID. For native iOS implementations, the client ID should be your app's bundle ID, not the service ID used in web apps. This difference can lead to the "Invalid id token" error if the wrong ID is used. 2. **Client Secret Generation**: Ensure your client secret is correctly generated. Apple requires a JSON Web Token (JWT) that should be dynamically generated using the `.p8` key file. Refer to the Apple Developer documentation for generating this secret, and remember that it should be included in the backend configuration, typically set as `clientSecret` [[1]](https://www.better-auth.com/docs/authentication/apple). 3. **Trusted Origins**: Confirm that `https://appleid.apple.com` is included in the `trustedOrigins` array in your Better Auth configuration. This is crucial for allowing communication with Apple's servers [[2]](https://github.com/better-auth/better-auth/pull/966). 4. **Debugging Information**: Double-check the token you are getting from Apple's sign-in request. Use tools to decode the JWT and ensure that its claims (like `aud` and `iss`) are correct and match your app’s setup. If you have gone through these steps and the issue persists, I recommend contacting the Better Auth team for more tailored assistance. You can tag either @bekacru or @ping__ for help! Have you tried any of these troubleshooting steps already? Let me know how it goes, or if you have more questions! _If you need more help, tag @better-auth-agent in a comment so I can respond._ <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@bytaesu commented on GitHub (Jan 22, 2026):

Hi @Alvi24,

Could you check whether the environment variables are being passed correctly to Convex at runtime?

<!-- gh-comment-id:3783998313 --> @bytaesu commented on GitHub (Jan 22, 2026): Hi @Alvi24, Could you check whether the environment variables are being passed correctly to Convex at runtime?
Author
Owner

@Alvi24 commented on GitHub (Jan 22, 2026):

Hi @Alvi24,

Could you check whether the environment variables are being passed correctly to Convex at runtime?

i checked and they seem to be correct
since apple sign-in with web flow works that says that the configuration is done right

<!-- gh-comment-id:3784039981 --> @Alvi24 commented on GitHub (Jan 22, 2026): > Hi [@Alvi24](https://github.com/Alvi24), > > Could you check whether the environment variables are being passed correctly to Convex at runtime? i checked and they seem to be correct since apple sign-in with web flow works that says that the configuration is done right
Author
Owner

@Alvi24 commented on GitHub (Jan 28, 2026):

Hi @Alvi24,

Is this issue fixed in a recent PR?

<!-- gh-comment-id:3811242711 --> @Alvi24 commented on GitHub (Jan 28, 2026): > Hi @Alvi24, > Is this issue fixed in a recent PR?
Author
Owner

@Paola3stefania commented on GitHub (Feb 17, 2026):

Hi @Alvi24 we found that the verifyIdToken function for Apple was missing error handling around the JWT verification step

When verification fails the raw error would propagate instead of returning a clean "Invalid id token" message. We've fixed this so you'll get a proper error going forward: see pr https://github.com/better-auth/better-auth/pull/8011

Did you double check your appBundleIdentifier configuration??

For native iOS sign-in,Apple sets the token's audience to your app's bundle ID (e.g. com.yourteam.yourapp),

Can you confirm for me this next steps?

  1. Your APPLE_APP_BUNDLE_IDENTIFIER environment variable is correctly set in your Convex deployment (not just locally)
  2. he value matches your actual iOS app bundle ID exactly
  3. Your config is nested under socialProviders like this:
socialProviders: {
  apple: {
    clientId: process.env.APPLE_CLIENT_ID,
    clientSecret: process.env.APPLE_CLIENT_SECRET,
    appBundleIdentifier: process.env.APPLE_APP_BUNDLE_IDENTIFIER,
  },
},

The web flow working confirms your clientId and clientSecret are fine, it's the appBundleIdentifier that matters for the native idToken path.

Let meknow if it works after verifying the config :)

<!-- gh-comment-id:3911101690 --> @Paola3stefania commented on GitHub (Feb 17, 2026): Hi @Alvi24 we found that the verifyIdToken function for Apple was missing error handling around the JWT verification step When verification fails the raw error would propagate instead of returning a clean "Invalid id token" message. We've fixed this so you'll get a proper error going forward: see pr https://github.com/better-auth/better-auth/pull/8011 Did you double check your `appBundleIdentifier` configuration?? For native iOS sign-in,Apple sets the token's audience to your app's bundle ID **(e.g. com.yourteam.yourapp)**, Can you confirm for me this next steps? 1. Your APPLE_APP_BUNDLE_IDENTIFIER environment variable is correctly set in your Convex deployment (not just locally) 2. he value matches your actual iOS app bundle ID exactly 3. Your config is nested under socialProviders like this: ```typescript socialProviders: { apple: { clientId: process.env.APPLE_CLIENT_ID, clientSecret: process.env.APPLE_CLIENT_SECRET, appBundleIdentifier: process.env.APPLE_APP_BUNDLE_IDENTIFIER, }, }, ``` The web flow working confirms your `clientId` and `clientSecret` are fine, it's the `appBundleIdentifier` that matters for the native idToken path. Let meknow if it works after verifying the config :)
Author
Owner

@Alvi24 commented on GitHub (Feb 17, 2026):

Hi @Alvi24 we found that the verifyIdToken function for Apple was missing error handling around the JWT verification step

When verification fails the raw error would propagate instead of returning a clean "Invalid id token" message. We've fixed this so you'll get a proper error going forward: see pr #8011

Did you double check your appBundleIdentifier configuration??

For native iOS sign-in,Apple sets the token's audience to your app's bundle ID (e.g. com.yourteam.yourapp),

Can you confirm for me this next steps?

  1. Your APPLE_APP_BUNDLE_IDENTIFIER environment variable is correctly set in your Convex deployment (not just locally)
  2. he value matches your actual iOS app bundle ID exactly
  3. Your config is nested under socialProviders like this:

socialProviders: {
apple: {
clientId: process.env.APPLE_CLIENT_ID,
clientSecret: process.env.APPLE_CLIENT_SECRET,
appBundleIdentifier: process.env.APPLE_APP_BUNDLE_IDENTIFIER,
},
},
The web flow working confirms your clientId and clientSecret are fine, it's the appBundleIdentifier that matters for the native idToken path.

Let me know if it works after verifying the config :)

yes i can confirm all of the 3 steps

<!-- gh-comment-id:3913630779 --> @Alvi24 commented on GitHub (Feb 17, 2026): > Hi [@Alvi24](https://github.com/Alvi24) we found that the verifyIdToken function for Apple was missing error handling around the JWT verification step > > When verification fails the raw error would propagate instead of returning a clean "Invalid id token" message. We've fixed this so you'll get a proper error going forward: see pr [#8011](https://github.com/better-auth/better-auth/pull/8011) > > Did you double check your `appBundleIdentifier` configuration?? > > For native iOS sign-in,Apple sets the token's audience to your app's bundle ID **(e.g. com.yourteam.yourapp)**, > > Can you confirm for me this next steps? > > 1. Your APPLE_APP_BUNDLE_IDENTIFIER environment variable is correctly set in your Convex deployment (not just locally) > 2. he value matches your actual iOS app bundle ID exactly > 3. Your config is nested under socialProviders like this: > > socialProviders: { > apple: { > clientId: process.env.APPLE_CLIENT_ID, > clientSecret: process.env.APPLE_CLIENT_SECRET, > appBundleIdentifier: process.env.APPLE_APP_BUNDLE_IDENTIFIER, > }, > }, > The web flow working confirms your `clientId` and `clientSecret` are fine, it's the `appBundleIdentifier` that matters for the native idToken path. > > Let me know if it works after verifying the config :) yes i can confirm all of the 3 steps
Author
Owner

@UsamaAshraf82 commented on GitHub (Mar 12, 2026):

+1

<!-- gh-comment-id:4046290185 --> @UsamaAshraf82 commented on GitHub (Mar 12, 2026): +1
Author
Owner

@hstemplewski commented on GitHub (Mar 25, 2026):

I have one question is it possible to provide more than one bundle id? I mean I have a case where 2 sepparate apps needs to authenticate using the same API. I am using the same package for expo-apple-authentication and I have configured the apple sign in for the second bundle ID, after trying to login I have Invalid token error

@Bekacru Maybe you can help

<!-- gh-comment-id:4126916156 --> @hstemplewski commented on GitHub (Mar 25, 2026): I have one question is it possible to provide more than one bundle id? I mean I have a case where 2 sepparate apps needs to authenticate using the same API. I am using the same package for expo-apple-authentication and I have configured the apple sign in for the second bundle ID, after trying to login I have `Invalid token` error @Bekacru Maybe you can help
Author
Owner

@hstemplewski commented on GitHub (Mar 25, 2026):

If you are looking for the answer how to pass multiple bundle ids to your apps use audience key in the apple social provider config. It's nothing about it in the docs, but it contains the array of strings and it is checked before the bundle indentifier key.

passing this config it works out of the box

apple: {
      clientId: env.APPLE_CLIENT_ID ?? '',
      clientSecret: env.APPLE_CLIENT_SECRET ?? '',
      audience: [
        env.APPLE_APP_BUNDLE_IDENTIFIER,
        env.APPLE_SECOND_APP_BUNDLE_IDENTIFIER,
        env.APPLE_CLIENT_ID,
      ],
      scope: ['email', 'name'],
      mapProfileToUser: (profile) => {
        const fullName = [
          profile.user?.name?.firstName,
          profile.user?.name?.lastName,
        ]
          .filter(Boolean)
          .join(' ')
          .trim();
        return {
          name:
            fullName ||
            profile.name ||
            profile.email?.split('@')[0] ||
            'Apple User',
        };
      },
    },

https://github.com/better-auth/better-auth/blob/main/packages/core/src/social-providers/apple.ts#L120-L130

<!-- gh-comment-id:4130088170 --> @hstemplewski commented on GitHub (Mar 25, 2026): If you are looking for the answer how to pass multiple bundle ids to your apps use `audience` key in the apple social provider config. It's nothing about it in the docs, but it contains the array of strings and it is checked before the bundle indentifier key. passing this config it works out of the box ```ts apple: { clientId: env.APPLE_CLIENT_ID ?? '', clientSecret: env.APPLE_CLIENT_SECRET ?? '', audience: [ env.APPLE_APP_BUNDLE_IDENTIFIER, env.APPLE_SECOND_APP_BUNDLE_IDENTIFIER, env.APPLE_CLIENT_ID, ], scope: ['email', 'name'], mapProfileToUser: (profile) => { const fullName = [ profile.user?.name?.firstName, profile.user?.name?.lastName, ] .filter(Boolean) .join(' ') .trim(); return { name: fullName || profile.name || profile.email?.split('@')[0] || 'Apple User', }; }, }, ``` https://github.com/better-auth/better-auth/blob/main/packages/core/src/social-providers/apple.ts#L120-L130
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#28161