BIO.RE
Authentication

Complete Login with 2FA

Second leg of the 2FA login flow — verify the 6-digit TOTP code (or a backup code) using the temp token from /auth/login.

POST /api/v1/auth/login/2fa — 🌐 Public · Rate limit: 10 req / hour · AUTH-P16

Completes the 2FA login flow. Call this AFTER POST /auth/login returned requiresTwoFactor: true with a tempToken. Returns full JWT pair (access + refresh cookie) on success.

Request

Body — LoginTwoFactorDto

FieldTypeRequiredValidationNotes
tempTokenstring (UUID)@IsUUID()Temp token from prior /auth/login response
codestring@IsString()6-digit TOTP code OR a backup code (8–10 chars)

Response

200 OKApiResponseOf<LoginResponseDto>

{
  "success": true,
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIs...",
    "expiresIn": 900
  }
}

Refresh token is set as httpOnly cookie scoped to .bio.re (not in body).

Errors

HTTPcode / i18nKeyReason
401auth.2fa.invalid_codeTOTP code wrong, or backup code already used
401auth.2fa.challenge_expiredtempToken expired (>5 min) or already consumed
429(throttle)Rate limit exceeded (10 req/hour)

Side effects

  1. Look up Challenge by tempToken; verify not expired + not consumed.
  2. Verify TOTP code against User.twoFactorSecret (decrypted with ENCRYPTION_MASTER_KEY) — OR match a backup code from BackupCode (deletes on use).
  3. Mark Challenge.usedAt = now() (single-use).
  4. Issue access + refresh JWT pair; create Session record.
  5. Audit log: auth.2fa.login.success or auth.2fa.login.failure.

Code samples

curl -X POST https://api.bio.re/api/v1/auth/login/2fa \
  -H 'Content-Type: application/json' \
  -c cookies.txt \
  -d '{
    "tempToken": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "code": "123456"
  }'
async function loginTwoFactor(tempToken: string, code: string): Promise<{ accessToken: string; expiresIn: number }> {
  const res = await fetch('https://api.bio.re/api/v1/auth/login/2fa', {
    method: 'POST',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ tempToken, code }),
  });
  const json = await res.json();
  if (!res.ok || !json.success) {
    throw Object.assign(new Error(json?.error?.message ?? '2FA failed'), {
      code: json?.error?.code,
    });
  }
  return json.data;
}
import { useMutation } from '@tanstack/react-query';

export function useLoginTwoFactor() {
  return useMutation({
    mutationFn: async (input: { tempToken: string; code: string }) => {
      const res = await fetch('/api/v1/auth/login/2fa', {
        method: 'POST',
        credentials: 'include',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(input),
      });
      const json = await res.json();
      if (!res.ok || !json.success) {
        throw Object.assign(new Error(json?.error?.message ?? '2FA failed'), {
          code: json?.error?.code,
          i18nKey: json?.error?.i18nKey,
        });
      }
      return json.data;
    },
    onSuccess: () => {
      router.push('/dashboard');
    },
  });
}

Try it

POST
/api/v1/auth/login/2fa

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

curl -X POST "https://loading/api/v1/auth/login/2fa" \  -H "Content-Type: application/json" \  -d '{    "tempToken": "294d4ead-63f9-4132-9588-f0638a182d96",    "code": "123456"  }'
{
  "success": true,
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIs...",
    "expiresIn": 900,
    "requiresTwoFactor": true,
    "tempToken": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}
{
  "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"
  }
}
{
  "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/auth.controller.ts144–164
DTO (request)apps/api-core/src/modules/auth/dto/two-factor.dto.ts9–15 (LoginTwoFactorDto)
DTO (response)apps/api-core/src/modules/auth/dto/response.dto.ts17–29 (LoginResponseDto)
Serviceapps/api-core/src/modules/auth/auth.service.tsverifyLoginTwoFactor()
Service (TOTP)apps/api-core/src/modules/auth/two-factor.service.tsTOTP verify
Prisma modelspackages/prisma/prisma/schema.prismaChallenge, BackupCode, User.twoFactorSecret, Session

On this page