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
| Field | Type | Required | Validation | Notes |
|---|---|---|---|---|
tempToken | string (UUID) | ✓ | @IsUUID() | Temp token from prior /auth/login response |
code | string | ✓ | @IsString() | 6-digit TOTP code OR a backup code (8–10 chars) |
Response
200 OK — ApiResponseOf<LoginResponseDto>
{
"success": true,
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"expiresIn": 900
}
}Refresh token is set as httpOnly cookie scoped to .bio.re (not in body).
Errors
| HTTP | code / i18nKey | Reason |
|---|---|---|
401 | auth.2fa.invalid_code | TOTP code wrong, or backup code already used |
401 | auth.2fa.challenge_expired | tempToken expired (>5 min) or already consumed |
429 | (throttle) | Rate limit exceeded (10 req/hour) |
Side effects
- Look up
ChallengebytempToken; verify not expired + not consumed. - Verify TOTP code against
User.twoFactorSecret(decrypted withENCRYPTION_MASTER_KEY) — OR match a backup code fromBackupCode(deletes on use). - Mark
Challenge.usedAt = now()(single-use). - Issue access + refresh JWT pair; create
Sessionrecord. - Audit log:
auth.2fa.login.successorauth.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
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
| Source | Path | Lines |
|---|---|---|
| Controller | apps/api-core/src/modules/auth/auth.controller.ts | 144–164 |
| DTO (request) | apps/api-core/src/modules/auth/dto/two-factor.dto.ts | 9–15 (LoginTwoFactorDto) |
| DTO (response) | apps/api-core/src/modules/auth/dto/response.dto.ts | 17–29 (LoginResponseDto) |
| Service | apps/api-core/src/modules/auth/auth.service.ts | verifyLoginTwoFactor() |
| Service (TOTP) | apps/api-core/src/modules/auth/two-factor.service.ts | TOTP verify |
| Prisma models | packages/prisma/prisma/schema.prisma | Challenge, BackupCode, User.twoFactorSecret, Session |