Description: A concise description of the changes is provided below.
Changelog: A changelog entry following Keep a Changelog format is included at the bottom.
Documentation: Docs repo update is prepared in the Open WebUI docs repo format and can be submitted separately if maintainers want it.
Dependencies: No new dependencies were added.
Testing: Manual tests were performed. Screenshots are included where applicable.
No Unchecked AI Code: AI-assisted changes have undergone human review and manual testing.
Self-Review: A self-review of the code has been performed.
Architecture: Smart defaults are used; TOTP is optional per user and disabled by default.
Git Hygiene: Atomic feature branch based on upstream/dev; no unrelated commits intended.
Title Prefix:feat: add optional TOTP two-factor authentication
Description
Adds optional per-user TOTP two-factor authentication for Open WebUI login.
Relates to #1225.
Refs #24650.
Addresses prior feedback from #11953 and #16461 around optional defaults, migration compatibility, metadata, rate limiting, backup-code behavior, and dependency scope.
This PR intentionally keeps QR-code rendering and admin-wide enforcement out of scope so the first TOTP slice stays small, dependency-free, and reviewable. Setup uses an authenticator-app compatible otpauth:// URI plus a manual setup key.
Manual Testing
Verified password login without TOTP remains unchanged.
Verified TOTP setup from Account settings.
Verified TOTP login challenge after password login.
Verified authenticator-code login.
Verified backup-code login.
Verified used backup codes cannot be reused.
Verified backup-code remaining count updates.
Verified admin can see and reset a user's TOTP status.
Verified OAuth callback/token-exchange paths return a TOTP challenge when local TOTP is enabled.
Verified sandbox UI manually.
Automated / Local Checks
git diff --check
Backend py_compile for touched Python files
Direct RFC HOTP/TOTP vector checks
Backup-code normalize/hash/verify checks
npm run test:frontend
./node_modules/.bin/vite build
Note: npm run build also passed, but the existing pyodide:fetch step pulled a newer black wheel and dirtied static/pyodide/pyodide-lock.json. That generated change was intentionally excluded from this PR.
Screenshots or Videos
Local MFA login challenge screenshot was captured during testing. Recommended PR screenshots before marking ready:
Account settings before TOTP is enabled.
TOTP setup key / authenticator URI state.
Backup codes shown immediately after enable/regenerate.
Admin user edit modal showing TOTP status/reset.
Changelog Entry
Description
Adds optional per-user TOTP two-factor authentication to protect Open WebUI login for self-hosted instances.
Added
Per-user TOTP setup and management in Account settings.
TOTP challenge support for password, LDAP, trusted-header/no-auth, OAuth callback, and OAuth token-exchange login flows.
One-time backup codes for account recovery.
Admin TOTP status and reset control for user recovery.
TOTP metadata including creation and last-used timestamps.
TOTP replay protection using the last-used time step, updated in the same locked transaction as verification.
Rate limiting for TOTP verification flows.
Changed
Login responses can now return an MFA challenge when TOTP is enabled for the user.
Deprecated
None.
Removed
None.
Fixed
None.
Security
TOTP secrets are encrypted with a key derived from WEBUI_SECRET_KEY.
Backup codes are hashed with bcrypt.
TOTP verification is rate-limited.
TOTP and backup-code verification update usage state atomically to avoid double-use races.
TOTP is disabled by default and must be explicitly enabled per user.
Multi-instance deployments should use shared rate-limiting state if Open WebUI supports/configures it for the deployment.
API keys remain bearer credentials and are not changed by login-time TOTP.
QR-code rendering and admin-wide TOTP enforcement are follow-up scope.
Contributor License Agreement
By submitting this pull request, I confirm that I have read and fully agree to the Contributor License Agreement (CLA), and I am providing my contributions under its terms.
Note
Deleting the CLA section will lead to immediate closure of your PR and it will not be merged in.
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.
## 📋 Pull Request Information
**Original PR:** https://github.com/open-webui/open-webui/pull/24841
**Author:** [@jeremy-windsor](https://github.com/jeremy-windsor)
**Created:** 5/17/2026
**Status:** ❌ Closed
**Base:** `dev` ← **Head:** `feat/totp-2fa`
---
### 📝 Commits (1)
- [`116192d`](https://github.com/open-webui/open-webui/commit/116192d98d13010aa5746f63ed772caad8dd1a6c) feat: add optional TOTP two-factor authentication
### 📊 Changes
**14 files changed** (+1680 additions, -75 deletions)
<details>
<summary>View changed files</summary>
➕ `backend/open_webui/migrations/versions/f6b90f8a2c1d_add_user_totp_table.py` (+48 -0)
📝 `backend/open_webui/models/auths.py` (+2 -0)
➕ `backend/open_webui/models/totp.py` (+206 -0)
📝 `backend/open_webui/routers/auths.py` (+326 -9)
📝 `backend/open_webui/routers/users.py` (+51 -0)
📝 `backend/open_webui/utils/oauth.py` (+75 -53)
➕ `backend/open_webui/utils/totp.py` (+122 -0)
📝 `src/lib/apis/auths/index.ts` (+190 -0)
📝 `src/lib/apis/users/index.ts` (+54 -0)
📝 `src/lib/components/admin/Users/UserList/EditUserModal.svelte` (+57 -3)
📝 `src/lib/components/chat/Settings/Account.svelte` (+8 -1)
➕ `src/lib/components/chat/Settings/Account/TOTP.svelte` (+335 -0)
📝 `src/routes/auth/+page.svelte` (+156 -9)
➕ `test/test_totp_utils.py` (+50 -0)
</details>
### 📄 Description
<!--
Critical Open WebUI PR requirements:
- Target branch: dev
- Keep the CLA section intact
-->
# Pull Request Checklist
**Before submitting, make sure you've checked the following:**
- [x] **Linked Issue/Discussion:** Relates to #1225. Also references #24650, #11953, and #16461.
- [x] **Target branch:** This PR targets `dev`.
- [x] **Description:** A concise description of the changes is provided below.
- [x] **Changelog:** A changelog entry following Keep a Changelog format is included at the bottom.
- [ ] **Documentation:** Docs repo update is prepared in the Open WebUI docs repo format and can be submitted separately if maintainers want it.
- [x] **Dependencies:** No new dependencies were added.
- [x] **Testing:** Manual tests were performed. Screenshots are included where applicable.
- [x] **No Unchecked AI Code:** AI-assisted changes have undergone human review and manual testing.
- [x] **Self-Review:** A self-review of the code has been performed.
- [x] **Architecture:** Smart defaults are used; TOTP is optional per user and disabled by default.
- [x] **Git Hygiene:** Atomic feature branch based on `upstream/dev`; no unrelated commits intended.
- [x] **Title Prefix:** `feat: add optional TOTP two-factor authentication`
## Description
Adds optional per-user TOTP two-factor authentication for Open WebUI login.
Relates to #1225.
Refs #24650.
Addresses prior feedback from #11953 and #16461 around optional defaults, migration compatibility, metadata, rate limiting, backup-code behavior, and dependency scope.
This PR intentionally keeps QR-code rendering and admin-wide enforcement out of scope so the first TOTP slice stays small, dependency-free, and reviewable. Setup uses an authenticator-app compatible `otpauth://` URI plus a manual setup key.
## Manual Testing
- Verified password login without TOTP remains unchanged.
- Verified TOTP setup from Account settings.
- Verified TOTP login challenge after password login.
- Verified authenticator-code login.
- Verified backup-code login.
- Verified used backup codes cannot be reused.
- Verified backup-code remaining count updates.
- Verified admin can see and reset a user's TOTP status.
- Verified OAuth callback/token-exchange paths return a TOTP challenge when local TOTP is enabled.
- Verified sandbox UI manually.
## Automated / Local Checks
- `git diff --check`
- Backend `py_compile` for touched Python files
- Direct RFC HOTP/TOTP vector checks
- Backup-code normalize/hash/verify checks
- `npm run test:frontend`
- `./node_modules/.bin/vite build`
Note: `npm run build` also passed, but the existing `pyodide:fetch` step pulled a newer `black` wheel and dirtied `static/pyodide/pyodide-lock.json`. That generated change was intentionally excluded from this PR.
## Screenshots or Videos
Local MFA login challenge screenshot was captured during testing. Recommended PR screenshots before marking ready:
- Account settings before TOTP is enabled.
- TOTP setup key / authenticator URI state.
- Backup codes shown immediately after enable/regenerate.
- Admin user edit modal showing TOTP status/reset.
# Changelog Entry
### Description
- Adds optional per-user TOTP two-factor authentication to protect Open WebUI login for self-hosted instances.
### Added
- Per-user TOTP setup and management in Account settings.
- TOTP challenge support for password, LDAP, trusted-header/no-auth, OAuth callback, and OAuth token-exchange login flows.
- One-time backup codes for account recovery.
- Admin TOTP status and reset control for user recovery.
- TOTP metadata including creation and last-used timestamps.
- TOTP replay protection using the last-used time step, updated in the same locked transaction as verification.
- Rate limiting for TOTP verification flows.
### Changed
- Login responses can now return an MFA challenge when TOTP is enabled for the user.
### Deprecated
- None.
### Removed
- None.
### Fixed
- None.
### Security
- TOTP secrets are encrypted with a key derived from `WEBUI_SECRET_KEY`.
- Backup codes are hashed with bcrypt.
- TOTP verification is rate-limited.
- TOTP and backup-code verification update usage state atomically to avoid double-use races.
- TOTP is disabled by default and must be explicitly enabled per user.
- No new dependencies were added.
### Breaking Changes
- None.
---
### Additional Information
- Changing `WEBUI_SECRET_KEY` invalidates encrypted TOTP secrets.
- Multi-instance deployments should use shared rate-limiting state if Open WebUI supports/configures it for the deployment.
- API keys remain bearer credentials and are not changed by login-time TOTP.
- QR-code rendering and admin-wide TOTP enforcement are follow-up scope.
### Contributor License Agreement
<!--
🚨 DO NOT DELETE THE TEXT BELOW 🚨
Keep the "Contributor License Agreement" confirmation text intact.
Deleting it will trigger the CLA-Bot to INVALIDATE your PR.
Your PR will NOT be reviewed or merged until you check the box below confirming that you have read and agree to the terms of the CLA.
-->
- [x] By submitting this pull request, I confirm that I have read and fully agree to the [Contributor License Agreement (CLA)](https://github.com/open-webui/open-webui/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT), and I am providing my contributions under its terms.
> [!NOTE]
> Deleting the CLA section will lead to immediate closure of your PR and it will not be merged in.
---
<sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
📋 Pull Request Information
Original PR: https://github.com/open-webui/open-webui/pull/24841
Author: @jeremy-windsor
Created: 5/17/2026
Status: ❌ Closed
Base:
dev← Head:feat/totp-2fa📝 Commits (1)
116192dfeat: add optional TOTP two-factor authentication📊 Changes
14 files changed (+1680 additions, -75 deletions)
View changed files
➕
backend/open_webui/migrations/versions/f6b90f8a2c1d_add_user_totp_table.py(+48 -0)📝
backend/open_webui/models/auths.py(+2 -0)➕
backend/open_webui/models/totp.py(+206 -0)📝
backend/open_webui/routers/auths.py(+326 -9)📝
backend/open_webui/routers/users.py(+51 -0)📝
backend/open_webui/utils/oauth.py(+75 -53)➕
backend/open_webui/utils/totp.py(+122 -0)📝
src/lib/apis/auths/index.ts(+190 -0)📝
src/lib/apis/users/index.ts(+54 -0)📝
src/lib/components/admin/Users/UserList/EditUserModal.svelte(+57 -3)📝
src/lib/components/chat/Settings/Account.svelte(+8 -1)➕
src/lib/components/chat/Settings/Account/TOTP.svelte(+335 -0)📝
src/routes/auth/+page.svelte(+156 -9)➕
test/test_totp_utils.py(+50 -0)📄 Description
Pull Request Checklist
Before submitting, make sure you've checked the following:
dev.upstream/dev; no unrelated commits intended.feat: add optional TOTP two-factor authenticationDescription
Adds optional per-user TOTP two-factor authentication for Open WebUI login.
Relates to #1225.
Refs #24650.
Addresses prior feedback from #11953 and #16461 around optional defaults, migration compatibility, metadata, rate limiting, backup-code behavior, and dependency scope.
This PR intentionally keeps QR-code rendering and admin-wide enforcement out of scope so the first TOTP slice stays small, dependency-free, and reviewable. Setup uses an authenticator-app compatible
otpauth://URI plus a manual setup key.Manual Testing
Automated / Local Checks
git diff --checkpy_compilefor touched Python filesnpm run test:frontend./node_modules/.bin/vite buildNote:
npm run buildalso passed, but the existingpyodide:fetchstep pulled a newerblackwheel and dirtiedstatic/pyodide/pyodide-lock.json. That generated change was intentionally excluded from this PR.Screenshots or Videos
Local MFA login challenge screenshot was captured during testing. Recommended PR screenshots before marking ready:
Changelog Entry
Description
Added
Changed
Deprecated
Removed
Fixed
Security
WEBUI_SECRET_KEY.Breaking Changes
Additional Information
WEBUI_SECRET_KEYinvalidates encrypted TOTP secrets.Contributor License Agreement
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.