BIO.RE
Authentication

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

FieldTypeRequiredValidationNotes
passwordstringMinLength(8)The current account password — checked against User.passwordHash via bcrypt
HeaderRequiredNotes
Authorization: Bearer <accessToken>JWT from POST /auth/login (which itself required 2FA)

Response

200 OKSuccessOnlyResponseDto

{
  "success": true
}
FieldTypeNotes
successbooleanAlways true on 200

Errors

HTTPcode / i18nKeyReason
400auth.2fa.no_passwordUser.passwordHash is null (account was created via OAuth-only — set a password first via POST /auth/change-password flow)
400auth.2fa.invalid_passwordPassword mismatch
401(guard)Missing / invalid bearer token
429(throttle)Rate limit exceeded (5 req/hour)

Side effects

  1. Lookup User.passwordHash; if null → throw no_password.
  2. bcrypt.compare(submittedPassword, passwordHash); if false → throw invalid_password.
  3. 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.
  4. 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

POST
/api/v1/auth/2fa/disable
AuthorizationBearer <token>

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

SourcePathLines
Controllerapps/api-core/src/modules/auth/two-factor.controller.ts64–74 (disable)
DTO (request)apps/api-core/src/modules/auth/dto/two-factor.dto.ts17–22 (DisableTwoFactorDto)
DTO (response)apps/api-core/src/common/dto/common-response.dto.tsSuccessOnlyResponseDto
Serviceapps/api-core/src/modules/auth/two-factor.service.ts130–161 (disable)
Prisma modelspackages/prisma/prisma/schema.prismaUser.twoFactorEnabled, User.twoFactorSecret, BackupCode, Session.revoked

On this page