{"record":{"id":"c9b7d0b4047e370a","repo":"HeyPuter/puter","slug":"oidc-revalidation-required","errorCode":"oidc_revalidation_required","errorMessage":"OIDC revalidation required","messagePattern":"OIDC revalidation required","errorType":"exception","errorClass":"HttpError","httpStatus":403,"severity":"error","filePath":"src/backend/core/http/middleware/userProtected.ts","lineNumber":221,"sourceCode":"\n        const isTemp = user.password === null && user.email === null;\n        if (isTemp) {\n            if (allowTemp) return next();\n            throw new HttpError(403, 'Temporary account', {\n                legacyCode: 'temporary_account',\n            });\n        }\n\n        const bodyPassword =\n            typeof req.body?.password === 'string' ? req.body.password : null;\n        if (bodyPassword) {\n            if (user.password === null) {\n                const fields = await buildRevalidateFields(\n                    config,\n                    oidcService,\n                    user,\n                );\n                throw new HttpError(403, 'OIDC revalidation required', {\n                    legacyCode: 'oidc_revalidation_required',\n                    fields,\n                });\n            }\n            let match = false;\n            try {\n                match = await bcrypt.compare(\n                    bodyPassword,\n                    String(user.password),\n                );\n            } catch {\n                match = false;\n            }\n            if (!match)\n                throw new HttpError(400, 'Password mismatch', {\n                    legacyCode: 'password_mismatch',\n                });\n            return next();","sourceCodeStart":203,"sourceCodeEnd":239,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/core/http/middleware/userProtected.ts#L203-L239","documentation":"Raised when an OIDC-only account (password is null — SSO-managed) submitted a `password` field in the request body. Password verification only applies to password accounts; OIDC accounts must revalidate identity through their OIDC provider. The error carries a `fields.revalidate_url` the GUI should open as a popup to re-run the OIDC flow.","triggerScenarios":"An SSO/OIDC-linked user posts a password on a userProtected route (e.g. the UI sent a password field by default without checking account type).","commonSituations":"GUI that always prompts for a password regardless of account type; an OIDC user trying their old pre-SSO password; a generic delete-account form not branched for SSO.","solutions":["Open `fields.revalidate_url` in a popup to revalidate via the OIDC provider.","Do not send a password for OIDC-only accounts.","Branch the GUI on account type (OIDC vs password) before submitting."],"exampleFix":"// before\nfetch('/user', { method:'DELETE', body: JSON.stringify({ password }) });\n// after (OIDC account) — surface fields.revalidate_url\nif (err.code === 'oidc_revalidation_required') {\n  window.open(err.fields.revalidate_url, 'oidc', 'popup');\n}","handlingStrategy":"try-catch","validationCode":"// Don't send a password for OIDC-only accounts:\nif (user.password == null) { delete body.password; }","typeGuard":"const isOidcOnlyAccount = (u) => !!(u && u.password == null);","tryCatchPattern":"try { await call(body); }\ncatch (e) {\n  if (e.code === 'oidc_revalidation_required' && e.fields?.revalidate_url) {\n    window.open(e.fields.revalidate_url, 'oidc', 'popup'); return;\n  }\n  throw e;\n}","preventionTips":["Branch the GUI on account type (OIDC vs password) before submitting.","Open the returned revalidate_url in a popup for SSO accounts.","Don't include a password field for OIDC-only accounts."],"tags":["oidc","sso","revalidation","user-protected"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}