{"record":{"id":"e462ba95796231f2","repo":"different-ai/openwork","slug":"oidc-discovery-issuer-does-not-match-the-configure","errorCode":null,"errorMessage":"OIDC discovery issuer does not match the configured issuer.","messagePattern":"OIDC discovery issuer does not match the configured issuer\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ee/apps/den-api/src/sso.ts","lineNumber":130,"sourceCode":"      userInfoEndpoint: input.userInfoEndpoint ?? undefined,\n      tokenEndpointAuthentication: input.tokenEndpointAuthentication ?? undefined,\n    }\n  }\n\n  const response = await fetch(getOidcDiscoveryUrl(input.issuer), {\n    headers: { accept: \"application/json\" },\n    signal: AbortSignal.timeout(10_000),\n  })\n  if (!response.ok) {\n    throw new Error(`OIDC discovery failed with ${response.status}. Enter manual OIDC endpoints or enable skip discovery.`)\n  }\n\n  const parsed = oidcDiscoverySchema.safeParse(await response.json())\n  if (!parsed.success) {\n    throw new Error(\"OIDC discovery document is missing required endpoints.\")\n  }\n  if (normalizeIssuer(parsed.data.issuer) !== normalizeIssuer(input.issuer)) {\n    throw new Error(\"OIDC discovery issuer does not match the configured issuer.\")\n  }\n\n  return {\n    skipDiscovery: true,\n    authorizationEndpoint: parsed.data.authorization_endpoint,\n    tokenEndpoint: parsed.data.token_endpoint,\n    jwksEndpoint: parsed.data.jwks_uri,\n    userInfoEndpoint: parsed.data.userinfo_endpoint,\n    tokenEndpointAuthentication: input.tokenEndpointAuthentication ?? undefined,\n  }\n}\n\nasync function getSsoProviderByProviderId(providerId: string) {\n  const rows = await db\n    .select()\n    .from(SsoProviderTable)\n    .where(eq(SsoProviderTable.providerId, providerId))\n    .limit(1)","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-api/src/sso.ts#L112-L148","documentation":"OIDC discovery documents include an issuer claim that must match the configured issuer. resolveOidcEndpoints compares them with trailing-slash normalization; any other mismatch throws this Error to prevent token-issuer confusion during verification.","triggerScenarios":"Configured issuer differs from the issuer field in the fetched discovery document beyond a trailing slash — wrong host, http vs https, port difference, path mismatch, or tenant placeholder not substituted.","commonSituations":"Using the generic Microsoft issuer instead of the tenant-specific one ({tenantid} placeholder not replaced); localhost vs production host; wrong protocol behind a reverse proxy; copying an issuer from a different environment.","solutions":["Copy the exact issuer value from the discovery document into your configuration (or vice versa)","For multi-tenant IdPs, replace placeholders (e.g. {tenantid} in Entra ID) with your real tenant id and use the tenant-specific endpoint","Check scheme, host, port, and path all match exactly after normalization"],"exampleFix":"// before\nissuer: 'https://sts.windows.net/common/' // discovery says tenant-specific\n// after\nissuer: 'https://sts.windows.net/9188040d-6c67-4c5b-b112-36a304b66dad/'","handlingStrategy":"validation","validationCode":"const doc = await (await fetch(getOidcDiscoveryUrl(issuer))).json()\nconst norm = (s: string) => s.replace(/\\/$/, '')\nif (norm(doc.issuer) !== norm(issuer)) throw new Error(`Issuer mismatch: configured ${issuer}, discovery says ${doc.issuer}`)","typeGuard":"function issuerMatches(configured: string, discovery: { issuer: string }): boolean {\n  const norm = (s: string) => s.replace(/\\/$/, '')\n  return norm(configured) === norm(discovery.issuer)\n}","tryCatchPattern":"try {\n  await resolveOidcEndpoints(input)\n} catch (e) {\n  if (e instanceof Error && e.message === 'OIDC discovery issuer does not match the configured issuer.') {\n    // show both issuers in the error so the admin can copy the correct one\n  } else throw e\n}","preventionTips":["Copy the issuer verbatim from the discovery document, never type it by hand","For multi-tenant IdPs (Entra ID, Auth0 custom domains), use the tenant-specific issuer","Check trailing slashes only after scheme/host/path all match"],"tags":["oidc","sso","issuer-mismatch","configuration"],"backgroundTag":"oidc-issuer-mismatch","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}