{"record":{"id":"2d5d9a5314a58456","repo":"danny-avila/LibreChat","slug":"idtoken-is-missing","errorCode":null,"errorMessage":"idToken is missing","messagePattern":"idToken is missing","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"api/strategies/appleStrategy.js","lineNumber":16,"sourceCode":"const jwt = require('jsonwebtoken');\nconst { logger } = require('@librechat/data-schemas');\nconst { Strategy: AppleStrategy } = require('passport-apple');\nconst socialLogin = require('./socialLogin');\n\n/**\n * Extract profile details from the decoded idToken\n * @param {Object} params - Parameters from the verify callback\n * @param {string} params.idToken - The ID token received from Apple\n * @param {Object} params.profile - The profile object (may contain partial info)\n * @returns {Object} - The extracted user profile details\n */\nconst getProfileDetails = ({ idToken, profile }) => {\n  if (!idToken) {\n    logger.error('idToken is missing');\n    throw new Error('idToken is missing');\n  }\n\n  const decoded = jwt.decode(idToken);\n\n  logger.debug(`Decoded Apple JWT: ${JSON.stringify(decoded, null, 2)}`);\n\n  return {\n    email: decoded.email,\n    id: decoded.sub,\n    avatarUrl: null, // Apple does not provide an avatar URL\n    username: decoded.email ? decoded.email.split('@')[0].toLowerCase() : `user_${decoded.sub}`,\n    name: decoded.name\n      ? `${decoded.name.firstName} ${decoded.name.lastName}`\n      : profile.displayName || null,\n    emailVerified: true, // Apple verifies the email\n  };\n};\n","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/danny-avila/LibreChat/blob/5ff282f9006c436e561de1afd39a481bea1ef0d8/api/strategies/appleStrategy.js#L1-L34","documentation":"Thrown by the Apple Sign-In strategy's profile extractor when the idToken passed into the passport-apple verify callback is falsy. The idToken is the JWT Apple issues that carries the user's `sub`, `email`, and `name` claims; without it there is no identity to decode (the next line runs `jwt.decode(idToken)`), so the strategy aborts before any user lookup. It is a hard guard — the error propagates out of `getProfileDetails` through `socialLogin`'s try/catch straight to passport as `cb(error)`, i.e. an internal error rather than a clean authentication failure.","triggerScenarios":"The passport-apple verify signature is `(accessToken, refreshToken, idToken, profile, cb)`. The throw fires when the `idToken` argument is `undefined`, `null`, or `''`. This happens when Apple's callback omits `id_token` (misconfigured Service ID / Sign In with Apple not enabled for the client), when the strategy is invoked with a stale/replayed authorization code, or during local development where the Apple callback URL is hit directly or proxied incorrectly and the token never arrives.","commonSituations":"Apple Service ID misconfigured (domain/return URL mismatch in Apple Developer console), `APPLE_CALLBACK_URL` env pointing at the wrong route, running the callback behind a proxy that strips the POST body, or a `passport-apple` version change that altered the verify arity so the positional `idToken` lands in the wrong slot.","solutions":["Verify the Apple Sign-In configuration in the Apple Developer console: the Service ID's Return URLs must include `${DOMAIN_SERVER}${APPLE_CALLBACK_URL}` exactly.","Confirm `APPLE_CLIENT_ID`, `APPLE_TEAM_ID`, `APPLE_KEY_ID`, and `APPLE_PRIVATE_KEY_PATH` are all set and the private key file is readable by the process.","Log the full verify-callback arguments at the top of `socialLogin`'s wrapper (or temporarily in `getProfileDetails`) to confirm which positional argument is actually undefined.","Ensure no reverse proxy or middleware consumes/rewrites the Apple callback POST body before passport-apple parses it."],"exampleFix":"// before\nconst getProfileDetails = ({ idToken, profile }) => {\n  if (!idToken) {\n    logger.error('idToken is missing');\n    throw new Error('idToken is missing');\n  }\n  const decoded = jwt.decode(idToken);\n// after — fail as a passport auth-failure with a stable code instead of a 500\nconst getProfileDetails = ({ idToken, profile }) => {\n  if (!idToken) {\n    logger.error('idToken is missing from Apple callback');\n    const err = new Error('Apple did not return an id_token');\n    err.code = 'APPLE_ID_TOKEN_MISSING';\n    throw err;\n  }\n  const decoded = jwt.decode(idToken);","handlingStrategy":"validation","validationCode":"// Before invoking the Apple strategy, assert the env is complete\nfunction assertAppleConfig() {\n  const required = ['APPLE_CLIENT_ID', 'APPLE_TEAM_ID', 'APPLE_KEY_ID', 'APPLE_PRIVATE_KEY_PATH', 'APPLE_CALLBACK_URL'];\n  const missing = required.filter((k) => !process.env[k]);\n  if (missing.length) throw new Error(`Apple Sign-In misconfigured: missing ${missing.join(', ')}`);\n}\nassertAppleConfig();","typeGuard":"function hasIdToken(args: unknown): args is { idToken: string } {\n  return typeof args === 'object' && args !== null &&\n    typeof (args as any).idToken === 'string' && (args as any).idToken.length > 0;\n}","tryCatchPattern":"// In socialLogin's wrapper — distinguish missing-token from other failures\ntry {\n  const details = getProfileDetails({ idToken, profile });\n} catch (err) {\n  if (err.message === 'idToken is missing') {\n    return cb(null, false, { message: 'Apple did not return an identity token' });\n  }\n  return cb(err);\n}","preventionTips":["Validate all APPLE_* env vars at startup and fail fast if any are unset when Apple Sign-In is enabled.","Log the arity/values of the passport-apple verify callback during integration to catch positional mismatches early.","Register the exact Return URL in the Apple Developer console and run an end-to-end test login before shipping."],"tags":["authentication","apple-signin","oauth","passport","jwt"],"backgroundTag":null,"analyzedSha":"5ff282f9006c436e561de1afd39a481bea1ef0d8","analyzedAt":"2026-08-12T21:38:08.145Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}