{"record":{"id":"4bdc462df3daa15b","repo":"decolua/9router","slug":"clientid-is-required-for-external-idp-refresh","errorCode":null,"errorMessage":"clientId is required for external_idp refresh","messagePattern":"clientId is required for external_idp refresh","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/lib/oauth/kiroExternalIdp.js","lineNumber":136,"sourceCode":"    providerSpecificData: {\n      profileArn,\n      region,\n      authMethod: \"external_idp\",\n      provider: \"CLIProxyAPI\",\n      clientId,\n      tokenEndpoint,\n      scope,\n    },\n  };\n}\n\nexport function buildExternalIdpRefreshParams(refreshToken, providerSpecificData = {}) {\n  const clientId = normalizeString(providerSpecificData.clientId || providerSpecificData.client_id);\n  const tokenEndpoint = validateMicrosoftTokenEndpoint(providerSpecificData.tokenEndpoint || providerSpecificData.token_endpoint);\n  const scope = normalizeScope(providerSpecificData.scope || providerSpecificData.scopes);\n\n  if (!refreshToken) throw new Error(\"refresh token is required\");\n  if (!clientId) throw new Error(\"clientId is required for external_idp refresh\");\n  if (!scope) throw new Error(\"scope is required for external_idp refresh\");\n\n  return {\n    tokenEndpoint,\n    body: new URLSearchParams({\n      grant_type: \"refresh_token\",\n      client_id: clientId,\n      refresh_token: refreshToken,\n      scope,\n    }),\n    providerSpecificData: {\n      ...providerSpecificData,\n      authMethod: \"external_idp\",\n      clientId,\n      tokenEndpoint,\n      scope,\n    },\n  };","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/kiroExternalIdp.js#L118-L154","documentation":"When building refresh parameters, the clientId must be present in providerSpecificData (as clientId or client_id). It is sent as the client_id field of the Microsoft token refresh POST. This guard fires when the stored provider-specific metadata lacks the client id — usually meaning the credential was created by a path that skipped normalization.","triggerScenarios":"buildExternalIdpRefreshParams(refreshToken, {}) or with metadata missing clientId/client_id, e.g. data constructed manually, migrated from an older schema, or with keys named 'clientID'/'application_id' that aren't recognized.","commonSituations":"Accounts migrated from an older storage layout that stored client metadata elsewhere; hand-crafted providerSpecificData in tests or scripts; a partial object spread that dropped clientId; importing via a path that never ran normalizeKiroExternalIdpAuth.","solutions":["Ensure accounts are created through normalizeKiroExternalIdpAuth so providerSpecificData.clientId is populated","Re-import the account with the complete CLIProxyAPI auth JSON including client_id","If migrating, backfill providerSpecificData.clientId from the original auth file before refreshing","Check key names: only clientId and client_id are read from providerSpecificData"],"exampleFix":"// before\nbuildExternalIdpRefreshParams(rt, { token_endpoint: url, scopes: 'openid' })\n// after\nbuildExternalIdpRefreshParams(rt, { token_endpoint: url, scopes: 'openid', client_id: '04b07795-8ddb-461a-bbee-02f9e1bf7b46' })","handlingStrategy":"validation","validationCode":"const psd = account.providerSpecificData ?? {};\nconst clientId = psd.clientId ?? psd.client_id;\nif (typeof clientId !== 'string' || !clientId.trim()) {\n  throw new Error('providerSpecificData missing clientId; re-import the account');\n}","typeGuard":"function hasRefreshClientId(psd) {\n  return typeof psd === 'object' && psd !== null &&\n    ['clientId', 'client_id'].some(k => typeof psd[k] === 'string' && psd[k].trim() !== '');\n}","tryCatchPattern":"try {\n  const params = buildExternalIdpRefreshParams(rt, account.providerSpecificData);\n} catch (e) {\n  if (e.message === 'clientId is required for external_idp refresh') {\n    console.error('Account metadata incomplete; re-import with full CLIProxyAPI auth JSON');\n  }\n  throw e;\n}","preventionTips":["Create providerSpecificData only through normalizeKiroExternalIdpAuth so clientId/tokenEndpoint/scope are all set","Backfill clientId when migrating accounts from older storage schemas","Use exactly clientId or client_id as the metadata key — variants like clientID are ignored","Add a startup integrity check that every external_idp account has clientId, tokenEndpoint, and scope"],"tags":["oauth","client-id","missing-field","token-refresh"],"backgroundTag":"missing-required-field","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}