{"record":{"id":"a9f9b5db52e5d39b","repo":"immich-app/immich","slug":"oauth-login-failed","errorCode":null,"errorMessage":"OAuth login failed","messagePattern":"OAuth login failed","errorType":"exception","errorClass":null,"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/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/repositories/oauth.repository.ts#L110-L146","documentation":"getProfileAndOAuthSid wraps any failure during the OAuth code exchange / token verification / profile retrieval into a single 'OAuth login failed' error, preserving the underlying cause. It is a top-level umbrella error for the whole OIDC login flow, so the original error is attached as `cause` and logged.","triggerScenarios":"Any exception inside the login flow: code/token exchange failure, ID token verification failure (bad issuer/audience/expiry), PKCE mismatch, client authentication rejection, or profile fetch failure during getProfileAndOAuthSid().","commonSituations":"Wrong client secret after rotation; redirect URI mismatch causing token exchange rejection; clock skew making tokens 'expired'; provider outage; state/PKCE verifier lost across a scaled deployment; user denied consent at the provider.","solutions":["Inspect the `cause` property / server logs ('OAuth login failed' with error) for the real failure","Verify clientId, clientSecret, and redirect URI match the provider's registered app settings","Check token endpoint auth method configuration matches what the provider expects","Confirm server clock is NTP-synchronized and the provider is reachable from the server"],"exampleFix":"// before\ntry { login(); } catch (e) { console.log(e.message); } // 'OAuth login failed'\n// after\ntry { login(); } catch (e) { console.log(e.message, e.cause); } // surfaces root cause","handlingStrategy":"try-catch","validationCode":"// before redirecting, sanity-check config\nassert(clientId && clientSecret, 'OAuth client credentials missing');\nassert(registeredRedirectUris.includes(redirectUri), 'redirectUri not registered with provider');","typeGuard":"const hasCause = (e: unknown): e is Error & { cause?: unknown } => e instanceof Error && 'cause' in e;","tryCatchPattern":"try {\n  await oauthRepo.getProfileAndOAuthSid(tokens);\n} catch (e) {\n  logger.error('OAuth login failed', { cause: (e as any).cause ?? e });\n  return redirect('/login?error=oauth');\n}","preventionTips":["Surface e.cause in logs — the umbrella message hides the root reason","Keep client secrets and redirect URIs in sync with the provider dashboard","Synchronize server clocks (NTP) to avoid token expiry false positives","Store PKCE verifier/state in a shared store when running multiple instances"],"tags":["oauth","authentication","login"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}