- Add 2FA setup, enable, disable, and backup code management - Integrate 2FA challenge flow into login process - Add frontend modal for 2FA configuration - Support backup codes for account recovery
5.3 KiB
Two-Factor Authentication Implementation
Note: This document should be deleted after PR approval. It serves as a reference for reviewers to understand the scope of the contribution.
Acknowledgments
Thanks to all contributors and authors from the Inte.Team for the great work on Nginx Proxy Manager. It saves us time and effort, and we're happy to contribute back to the project.
Overview
Add TOTP-based two-factor authentication to the login flow. Users can enable 2FA from their profile settings, scan a QR code with any authenticator app (Google Authenticator, Authy, etc.), and will be required to enter a 6-digit code on login.
Current Authentication Flow
POST /tokens {identity, secret}
-> Validate user exists and is not disabled
-> Verify password against auth.secret
-> Return JWT token
Proposed 2FA Flow
POST /tokens {identity, secret}
-> Validate user exists and is not disabled
-> Verify password against auth.secret
-> If 2FA enabled:
Return {requires_2fa: true, challenge_token: <short-lived JWT>}
-> Else:
Return {token: <JWT>, expires: <timestamp>}
POST /tokens/2fa {challenge_token, code}
-> Validate challenge_token
-> Verify TOTP code against user's secret
-> Return {token: <JWT>, expires: <timestamp>}
Database Changes
Extend the existing auth.meta JSON column to store 2FA data:
{
"totp_secret": "<encrypted-secret>",
"totp_enabled": true,
"totp_enabled_at": "<timestamp>",
"backup_codes": ["<hashed-code-1>", "<hashed-code-2>", ...]
}
No new tables required. The auth.meta column is already designed for this purpose.
Backend Changes
New Files
-
backend/internal/2fa.js- Core 2FA logicgenerateSecret()- Generate TOTP secretgenerateQRCodeURL(user, secret)- Generate otpauth URLverifyCode(secret, code)- Verify TOTP codegenerateBackupCodes()- Generate 8 backup codesverifyBackupCode(user, code)- Verify and consume backup code
-
backend/routes/2fa.js- 2FA management endpointsGET /users/:id/2fa- Get 2FA statusPOST /users/:id/2fa/setup- Start 2FA setup, return QR codePUT /users/:id/2fa/enable- Verify code and enable 2FADELETE /users/:id/2fa- Disable 2FA (requires code)GET /users/:id/2fa/backup-codes- View remaining backup codes countPOST /users/:id/2fa/backup-codes- Regenerate backup codes
Modified Files
-
backend/internal/token.js- Update
getTokenFromEmail()to check for 2FA - Add
verifyTwoFactorChallenge()function - Add
createChallengeToken()for short-lived 2FA tokens
- Update
-
backend/routes/tokens.js- Add
POST /tokens/2faendpoint
- Add
-
backend/index.js- Register new 2FA routes
Dependencies
Add to package.json:
"otplib": "^12.0.1"
Frontend Changes
New Files
frontend/src/pages/Login2FA/index.tsx- 2FA code entry pagefrontend/src/modals/TwoFactorSetupModal.tsx- Setup wizard modalfrontend/src/api/backend/twoFactor.ts- 2FA API functionsfrontend/src/api/backend/verify2FA.ts- Token verification
Modified Files
-
frontend/src/api/backend/responseTypes.ts- Add
TwoFactorChallengeResponsetype - Add
TwoFactorStatusResponsetype
- Add
-
frontend/src/context/AuthContext.tsx- Add
twoFactorRequiredstate - Add
challengeTokenstate - Update
login()to handle 2FA response - Add
verify2FA()function
- Add
-
frontend/src/pages/Login/index.tsx- Handle 2FA challenge response
- Redirect to 2FA entry when required
-
frontend/src/pages/Settings/(or user profile)- Add 2FA enable/disable section
Dependencies
Add to package.json:
"qrcode.react": "^3.1.0"
API Endpoints Summary
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /tokens | No | Login (returns challenge if 2FA) |
| POST | /tokens/2fa | Challenge | Complete 2FA login |
| GET | /users/:id/2fa | JWT | Get 2FA status |
| POST | /users/:id/2fa/setup | JWT | Start setup, get QR code |
| PUT | /users/:id/2fa/enable | JWT | Verify and enable |
| DELETE | /users/:id/2fa | JWT | Disable (requires code) |
| GET | /users/:id/2fa/backup-codes | JWT | Get backup codes count |
| POST | /users/:id/2fa/backup-codes | JWT | Regenerate codes |
Security Considerations
- Challenge tokens expire in 5 minutes
- TOTP secrets encrypted at rest
- Backup codes hashed with bcrypt
- Rate limit on 2FA attempts (5 attempts, 15 min lockout)
- Backup codes single-use only
- 2FA disable requires valid TOTP code
Implementation Order
- Backend: Add
otplibdependency - Backend: Create
internal/2fa.jsmodule - Backend: Update
internal/token.jsfor challenge flow - Backend: Add
POST /tokens/2faroute - Backend: Create
routes/2fa.jsfor management - Frontend: Add
qrcode.reactdependency - Frontend: Update API types and functions
- Frontend: Update AuthContext for 2FA state
- Frontend: Create Login2FA page
- Frontend: Update Login to handle 2FA
- Frontend: Add 2FA settings UI
Testing
- Enable 2FA for user
- Login with password only - should get challenge
- Submit correct TOTP - should get token
- Submit wrong TOTP - should fail
- Use backup code - should work once
- Disable 2FA - should require valid code
- Login after disable - should work without 2FA