different-ai/openwork · error

OIDC discovery issuer does not match the configured issuer.

Error message

OIDC discovery issuer does not match the configured issuer.

What it means

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.

Source

Thrown at ee/apps/den-api/src/sso.ts:130

      userInfoEndpoint: input.userInfoEndpoint ?? undefined,
      tokenEndpointAuthentication: input.tokenEndpointAuthentication ?? undefined,
    }
  }

  const response = await fetch(getOidcDiscoveryUrl(input.issuer), {
    headers: { accept: "application/json" },
    signal: AbortSignal.timeout(10_000),
  })
  if (!response.ok) {
    throw new Error(`OIDC discovery failed with ${response.status}. Enter manual OIDC endpoints or enable skip discovery.`)
  }

  const parsed = oidcDiscoverySchema.safeParse(await response.json())
  if (!parsed.success) {
    throw new Error("OIDC discovery document is missing required endpoints.")
  }
  if (normalizeIssuer(parsed.data.issuer) !== normalizeIssuer(input.issuer)) {
    throw new Error("OIDC discovery issuer does not match the configured issuer.")
  }

  return {
    skipDiscovery: true,
    authorizationEndpoint: parsed.data.authorization_endpoint,
    tokenEndpoint: parsed.data.token_endpoint,
    jwksEndpoint: parsed.data.jwks_uri,
    userInfoEndpoint: parsed.data.userinfo_endpoint,
    tokenEndpointAuthentication: input.tokenEndpointAuthentication ?? undefined,
  }
}

async function getSsoProviderByProviderId(providerId: string) {
  const rows = await db
    .select()
    .from(SsoProviderTable)
    .where(eq(SsoProviderTable.providerId, providerId))
    .limit(1)

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Copy the exact issuer value from the discovery document into your configuration (or vice versa)
  2. For multi-tenant IdPs, replace placeholders (e.g. {tenantid} in Entra ID) with your real tenant id and use the tenant-specific endpoint
  3. Check scheme, host, port, and path all match exactly after normalization

Example fix

// before
issuer: 'https://sts.windows.net/common/' // discovery says tenant-specific
// after
issuer: 'https://sts.windows.net/9188040d-6c67-4c5b-b112-36a304b66dad/'
Defensive patterns

Strategy: validation

Validate before calling

const doc = await (await fetch(getOidcDiscoveryUrl(issuer))).json()
const norm = (s: string) => s.replace(/\/$/, '')
if (norm(doc.issuer) !== norm(issuer)) throw new Error(`Issuer mismatch: configured ${issuer}, discovery says ${doc.issuer}`)

Type guard

function issuerMatches(configured: string, discovery: { issuer: string }): boolean {
  const norm = (s: string) => s.replace(/\/$/, '')
  return norm(configured) === norm(discovery.issuer)
}

Try / catch

try {
  await resolveOidcEndpoints(input)
} catch (e) {
  if (e instanceof Error && e.message === 'OIDC discovery issuer does not match the configured issuer.') {
    // show both issuers in the error so the admin can copy the correct one
  } else throw e
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/e462ba95796231f2. Report an issue: GitHub.