Disable 2FA
Turn 2FA off after re-confirming the account password. Wipes the TOTP secret, deletes all backup codes, and revokes all sessions.
POST /api/v1/auth/2fa/disable — 🔑 Bearer · Rate limit: 5 req / hour
Disables 2FA on the current account. Requires the current account password as a re-confirmation step (in addition to the bearer token). On success: clears User.twoFactorSecret, deletes every BackupCode, and revokes every Session for the user.
Sessions are revoked, including the one calling this endpoint. The user must re-login afterwards (no longer with 2FA). Plan a redirect to the login screen on success.
The password check exists because the bearer token alone shouldn't be enough to weaken account security. A stolen / leaked access token cannot disable 2FA without also knowing the password.
Request
Body — DisableTwoFactorDto
| Field | Type | Required | Validation | Notes |
|---|---|---|---|---|
password | string | ✓ | MinLength(8) | The current account password — checked against User.passwordHash via bcrypt |
| Header | Required | Notes |
|---|---|---|
Authorization: Bearer <accessToken> | ✓ | JWT from POST /auth/login (which itself required 2FA) |
Response
200 OK — SuccessOnlyResponseDto
{
"success": true
}| Field | Type | Notes |
|---|---|---|
success | boolean | Always true on 200 |
Errors
| HTTP | code / i18nKey | Reason |
|---|---|---|
400 | auth.2fa.no_password | User.passwordHash is null (account was created via OAuth-only — set a password first via POST /auth/change-password flow) |
400 | auth.2fa.invalid_password | Password mismatch |
401 | (guard) | Missing / invalid bearer token |
429 | (throttle) | Rate limit exceeded (5 req/hour) |
Side effects
- Lookup
User.passwordHash; if null → throwno_password. bcrypt.compare(submittedPassword, passwordHash); if false → throwinvalid_password.- Inside one transaction:
User.twoFactorEnabled = false,User.twoFactorSecret = null.DELETE FROM BackupCode WHERE userId = :userId(all of them).UPDATE Session SET revoked = true, revokedAt = now() WHERE userId = :userId AND revoked = false.
- Audit log:
[2fa] Disabled for user {userId}.
Code samples
curl -X POST https://api.bio.re/api/v1/auth/2fa/disable \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"password": "current-account-password"}'async function disableTwoFactor(accessToken: string, password: string): Promise<void> {
const res = await fetch('https://api.bio.re/api/v1/auth/2fa/disable', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ password }),
});
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? '2FA disable failed'), {
code: json?.error?.code,
});
}
}import { useMutation, useQueryClient } from '@tanstack/react-query';
export function useDisableTwoFactor() {
const qc = useQueryClient();
return useMutation({
mutationFn: async (password: string) => {
const res = await fetch('/api/v1/auth/2fa/disable', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password }),
});
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? '2FA disable failed'), {
code: json?.error?.code,
i18nKey: json?.error?.i18nKey,
});
}
},
onSuccess: () => {
// Sessions revoked server-side — drop cached identity, force re-login
qc.invalidateQueries({ queryKey: ['auth', 'me'] });
},
});
}Try it
Authorization
bearer In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
curl -X POST "https://loading/api/v1/auth/2fa/disable" \ -H "Content-Type: application/json" \ -d '{ "password": "stringst" }'{
"success": true
}{
"success": false,
"error": {
"code": "AUTH_UNAUTHORIZED",
"message": "Invalid credentials",
"i18nKey": "auth.login.invalid_credentials",
"i18nVars": {
"field": "email"
},
"details": [
{
"message": "email must be an email"
}
],
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}Source
| Source | Path | Lines |
|---|---|---|
| Controller | apps/api-core/src/modules/auth/two-factor.controller.ts | 64–74 (disable) |
| DTO (request) | apps/api-core/src/modules/auth/dto/two-factor.dto.ts | 17–22 (DisableTwoFactorDto) |
| DTO (response) | apps/api-core/src/common/dto/common-response.dto.ts | SuccessOnlyResponseDto |
| Service | apps/api-core/src/modules/auth/two-factor.service.ts | 130–161 (disable) |
| Prisma models | packages/prisma/prisma/schema.prisma | User.twoFactorEnabled, User.twoFactorSecret, BackupCode, Session.revoked |
Verify TOTP & Activate 2FA
Verify the 6-digit TOTP code against the previously stored secret. On success, flip 2FA on, generate one-time backup codes, and revoke all existing sessions.
Regenerate Backup Codes
Issue a fresh set of backup codes after the user proves their authenticator still works (TOTP code, not backup code). Old codes are deleted atomically.