{"record":{"id":"a9f9b5db52e5d39b","repo":"immich-app/immich","slug":"oauth-login-failed","errorCode":null,"errorMessage":"OAuth login failed","messagePattern":"OAuth login failed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/src/repositories/oauth.repository.ts","lineNumber":128,"sourceCode":"        if (typeof claims?.sid === 'string') {\n          sid = claims.sid;\n        }\n      }\n\n      return { profile, sid, idToken: tokens.id_token };\n    } catch (error: Error | any) {\n      if (error.message.includes('unexpected JWT alg received')) {\n        this.logger.warn(\n          [\n            'Algorithm mismatch. Make sure the signing algorithm is set correctly in the OAuth settings.',\n            'Or, that you have specified a signing key in your OAuth provider.',\n          ].join(' '),\n        );\n      }\n\n      this.logger.error('OAuth login failed', error);\n\n      throw new Error('OAuth login failed', { cause: error });\n    }\n  }\n\n  async getProfilePicture(url: string) {\n    const response = await fetch(url);\n    if (!response.ok) {\n      throw new Error(`Failed to fetch picture: ${response.statusText}`);\n    }\n\n    return response.arrayBuffer();\n  }\n\n  private jwksClients: Map<string, JWTVerifyGetKey> = new Map(); // useful for caching and performnce\n  async validateLogoutToken(config: OAuthConfig, logoutToken: string): Promise<{ sub?: string; sid?: string } | null> {\n    const client = await this.getClient(config);\n    const algorithm = client.clientMetadata().id_token_signed_response_alg ?? 'RS256';\n    let keyOrGetter: Uint8Array | JWTVerifyGetKey;\n","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/immich-app/immich/blob/5666d57f15a66bd5518119c5d9f4d2b62f3a86c1/server/src/repositories/oauth.repository.ts#L110-L146","documentation":"`getProfileAndOAuthSid` exchanges the OAuth code (or verifies the token) with the provider and fetches the user profile. Any failure in that flow — token exchange, profile fetch, network errors, provider rejections — is caught, logged, and rethrown as a generic Error('OAuth login failed') with the underlying error as `cause`. The generic message hides details, so the `cause` (and server logs) must be inspected to find the real reason.","triggerScenarios":"Calling the OAuth login endpoint with an invalid/expired/already-used authorization `code`; mismatched client_id/client_secret or redirect_uri vs. what the provider expects; the provider's token or userinfo endpoint being unreachable or returning 4xx/5xx; malformed id_token/access_token (wrong issuer, audience, expired); network failures to the provider.","commonSituations":"Incorrect OAuth client configuration in server settings (wrong client secret, redirect URI not registered); user taking too long and the code expiring; replaying a callback URL; provider outage or self-hosted provider (e.g. Authentik/Keycloak) behind DNS that the server can't resolve; clock skew invalidating tokens.","solutions":["Log/inspect `error.cause` (also check server logs — this.logger.error already logs the original error) to see the real provider failure.","Verify the OAuth provider config: client_id, client_secret, issuer URL, and redirect URI exactly match the provider app registration.","Test connectivity from the server to the provider's token/userinfo endpoints (curl the discovery URL) — self-hosted providers often fail due to Docker DNS/network isolation.","Ensure the authorization code is single-use and recent: don't replay callback URLs or reuse codes.","Check server clock sync (NTP) if tokens are rejected as expired."],"exampleFix":"try {\n  await loginWithOAuth(code);\n} catch (e) {\n  console.error('root cause:', (e as Error).cause); // see real provider error\n}","handlingStrategy":"try-catch","validationCode":"// pre-flight: verify provider discovery is reachable and config is set\nconst cfg = await fetch(`${issuerUrl}/.well-known/openid-configuration`).then(r => r.json());\nif (!cfg.token_endpoint || !cfg.userinfo_endpoint) throw new Error('provider config incomplete');","typeGuard":"function hasCause(e: unknown): e is Error & { cause: unknown } {\n  return e instanceof Error && 'cause' in e;\n}","tryCatchPattern":"try {\n  const profile = await getProfileAndOAuthSid(code);\n} catch (e) {\n  if (hasCause(e)) console.error('OAuth failure root cause:', e.cause);\n  // redirect user to login with a generic error; never expose provider details\n}","preventionTips":["Always log/inspect error.cause — the message itself is generic.","Validate client_id, client_secret, redirect_uri against the provider app registration before deploying.","Never replay or cache authorization codes; they are single-use and short-lived.","Test provider connectivity from inside the server's network/container.","Keep server clocks NTP-synced to avoid token expiry rejections."],"tags":["oauth","authentication","network","config"],"backgroundTag":"oauth-login-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"}