{"record":{"id":"d120f2f218f19519","repo":"actualbudget/actual","slug":"openid-grant-failed","errorCode":"openid-grant-failed","errorMessage":"openid-grant-failed","messagePattern":"openid-grant-failed","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/sync-server/src/accounts/openid.ts","lineNumber":292,"sourceCode":"              countUsersWithUserName === 0 ? 'ADMIN' : 'BASIC',\n            ],\n          );\n\n          if (countUsersWithUserName === 0) {\n            const userFromPasswordMethod = getUserByUsername('');\n            if (userFromPasswordMethod) {\n              transferAllFilesFromUser(userId, userFromPasswordMethod.user_id);\n            }\n          }\n        } else {\n          const { id: userIdFromDb, display_name: displayName } =\n            accountDb.first(\n              'SELECT id, display_name FROM users WHERE user_name = ? and enabled = 1',\n              [identity],\n            ) || {};\n\n          if (userIdFromDb == null) {\n            throw new Error('openid-grant-failed');\n          }\n\n          if (!displayName && userInfo.name) {\n            accountDb.mutate('UPDATE users set display_name = ? WHERE id = ?', [\n              userInfo.name,\n              userIdFromDb,\n            ]);\n          }\n\n          userId = userIdFromDb;\n        }\n      });\n    } catch (error) {\n      if (error.message === 'user-already-exists') {\n        return { error: 'user-already-exists' };\n      } else if (error.message === 'openid-grant-failed') {\n        return { error: 'openid-grant-failed' };\n      } else {","sourceCodeStart":274,"sourceCodeEnd":310,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/accounts/openid.ts#L274-L310","documentation":"loginWithOpenIdFinalize completes the OpenID Connect callback: it exchanges the code, reads the identity claim, and looks up an enabled user matching that identity in the accounts DB. If no enabled user matches, it throws Error('openid-grant-failed') (code openid-grant-failed), aborting the SSO login.","triggerScenarios":"A user completes the IdP login but the 'preferred_username'/identity claim does not match any user_name in the users table, or the matching user row has enabled = 0 (disabled account).","commonSituations":"IdP username changed (e.g. company rename to email-based logins) while Actual's user_name stayed the old value; user created in the IdP but never added in Actual's users management screen; user disabled in Actual but still active in the IdP.","solutions":["Create or update the user in Actual's server user-management so user_name exactly matches the IdP identity claim","Re-enable the user if enabled = 0 (UPDATE users SET enabled = 1 WHERE user_name = ...)","Verify the claim used for identity mapping matches case and format (check server logs for the value)","Confirm the OpenID issuer/client config so the expected claim is being read"],"exampleFix":"// before: IdP sends 'jane.doe@corp.com', Actual user_name = 'jdoe'\n// after (server user management or SQL)\nUPDATE users SET user_name = 'jane.doe@corp.com' WHERE user_name = 'jdoe';\nUPDATE users SET enabled = 1 WHERE user_name = 'jane.doe@corp.com';","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const { error, url } = await loginWithOpenIdFinalize(...);\n} catch (e) {\n  if (e.message === 'openid-grant-failed') {\n    render('Your account is not enabled on this Actual server or your username does not match. Contact the server admin.');\n  } else throw e;\n}","preventionTips":["Keep Actual's users table in sync with the IdP (same identity claim values)","Enable users in Actual before granting them access in the IdP","After IdP username changes, update user_name in Actual immediately","Check server logs for the identity value received during failed grants"],"tags":["openid","authentication","sso","sync-server"],"backgroundTag":"openid-grant-failed","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}