{"record":{"id":"043ae2c4392af2d8","repo":"immich-app/immich","slug":"error-in-oauth-discovery-error","errorCode":null,"errorMessage":"Error in OAuth discovery: ${error}","messagePattern":"Error in OAuth discovery: (.+?)","errorType":"exception","errorClass":"InternalServerErrorException","httpStatus":500,"severity":"critical","filePath":"server/src/repositories/oauth.repository.ts","lineNumber":223,"sourceCode":"    try {\n      return await discovery(\n        new URL(issuerUrl),\n        clientId,\n        {\n          client_secret: clientSecret,\n          response_types: ['code'],\n          userinfo_signed_response_alg: profileSigningAlgorithm === 'none' ? undefined : profileSigningAlgorithm,\n          id_token_signed_response_alg: signingAlgorithm,\n        },\n        this.getTokenAuthMethod(tokenEndpointAuthMethod, clientSecret),\n        {\n          execute: allowInsecureRequests ? [allowInsecureRequestsExecute] : [],\n          timeout,\n        },\n      );\n    } catch (error: any | AggregateError) {\n      this.logger.error('Error in OAuth discovery', error);\n      throw new InternalServerErrorException(`Error in OAuth discovery: ${error}`, { cause: error });\n    }\n  }\n\n  private getTokenAuthMethod(tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod, clientSecret?: string) {\n    if (!clientSecret) {\n      return None();\n    }\n\n    switch (tokenEndpointAuthMethod) {\n      case OAuthTokenEndpointAuthMethod.ClientSecretPost: {\n        return ClientSecretPost(clientSecret);\n      }\n\n      case OAuthTokenEndpointAuthMethod.ClientSecretBasic: {\n        return ClientSecretBasic(clientSecret);\n      }\n\n      default: {","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/immich-app/immich/blob/5666d57f15a66bd5518119c5d9f4d2b62f3a86c1/server/src/repositories/oauth.repository.ts#L205-L241","documentation":"`getClient` performs OIDC discovery against the provider's well-known configuration using openid-client and wraps any failure in an InternalServerErrorException('Error in OAuth discovery: ...'). The original error (which may be an AggregateError from multiple discovery endpoints) is attached as `cause`. This means the server could not obtain/validate the provider's OIDC metadata.","triggerScenarios":"The issuer URL is wrong or lacks a valid `/.well-known/openid-configuration`; the provider is unreachable from the server (DNS, firewall, Docker network); the provider uses plain HTTP while secure fetch is enforced (allowInsecureRequests not enabled); TLS certificate errors (self-signed certs); discovery document missing required fields (issuer mismatch, no supported algorithms).","commonSituations":"Misconfigured issuer URL in server settings (trailing path mistakes, http vs https); self-hosted Keycloak/Authentik not reachable from the server container; self-signed or internal CA certificates; provider behind mTLS or an auth-gated proxy.","solutions":["Read `error.cause` (and server logs — the original error is logged) to see the underlying failure: DNS, TLS, or HTTP status.","Open `<issuer>/.well-known/openid-configuration` from the server (curl inside the container) and verify it returns valid JSON with matching issuer.","Fix the issuer URL in OAuth settings — it must exactly match the `issuer` claim in the discovery document.","For self-signed/internal CAs, install the CA cert in the server's trust store or set NODE_EXTRA_CA_CERTS.","For plain-HTTP internal providers, enable insecure request allowance (allowInsecureRequests) — only on trusted internal networks."],"exampleFix":"// before\nissuerUrl: 'https://keycloak:8080/realms/myrealm' // server can't reach 'keycloak'\n// after\nissuerUrl: 'https://keycloak.internal:8080/realms/myrealm' // resolvable from server\n// or curl from inside the container first to verify discovery JSON","handlingStrategy":"validation","validationCode":"// verify discovery before configuring/using the client\nconst res = await fetch(`${issuerUrl}/.well-known/openid-configuration`);\nif (!res.ok) throw new Error(`discovery HTTP ${res.status}`);\nconst doc = await res.json();\nif (doc.issuer !== issuerUrl) throw new Error('issuer mismatch');","typeGuard":"function isInternalServerError(e: unknown): e is { status: number; response: string } {\n  return typeof e === 'object' && e !== null && 'status' in e && (e as any).status === 500;\n}","tryCatchPattern":"try {\n  const client = await getClient(issuerUrl, clientId, clientSecret);\n} catch (e) {\n  // cause holds the original discovery error (may be AggregateError)\n  console.error('discovery failed:', (e as any).cause ?? e);\n  throw new ServiceUnavailable('OAuth provider unreachable');\n}","preventionTips":["Curl the well-known endpoint from inside the server/container before saving OAuth settings.","Make the issuer URL match the discovery document's issuer claim exactly (scheme, host, port, path).","Trust internal/self-signed CAs properly (trust store or NODE_EXTRA_CA_CERTS) instead of disabling TLS.","Only enable insecure HTTP discovery on trusted internal networks.","Check Docker networking: container hostnames must resolve from the ML/API container, not just the host."],"tags":["oauth","oidc","discovery","network","config","tls"],"backgroundTag":"oidc-discovery-failed","analyzedSha":"5666d57f15a66bd5518119c5d9f4d2b62f3a86c1","analyzedAt":"2026-09-01T05:20:49.208Z","contentChangedAt":"2026-09-01T05:20:49.208Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}