BIO.RE
Authentication

Login with Email + Password

Authenticate an existing account. Returns JWT access + refresh tokens, OR `requiresTwoFactor` with a temp token.

POST /api/v1/auth/login — 🌐 Public · Rate limit: 20 req / hour · Captcha: required

Authenticates with email + password. Two flows:

  • Standard → returns accessToken + expiresIn + sets biore_refresh httpOnly cookie.
  • 2FA enabled → returns requiresTwoFactor: true + tempToken. Client must call POST /auth/login/2fa next.

The refresh token is set as an httpOnly, secure, SameSite=Strict cookie scoped to .bio.re. It is not in the response body — clients should not read it.

Request

Body — LoginDto

FieldTypeRequiredValidationNotes
emailstring (RFC 5322)Trimmed + lowercasedLookup against User.email
passwordstringIsString()bcrypt-compared against User.passwordHash
captchaTokenstringValidated by CaptchaGuard (admin-managed provider)Required if captcha enabled
turnstileTokenstringDeprecated alias for captchaToken (1 sprint backwards-compat)

Response

200 OK — standard flow (ApiResponseOf<LoginResponseDto>)

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

200 OK — 2FA flow (LoginResponseDto with requiresTwoFactor)

{
  "success": true,
  "data": {
    "requiresTwoFactor": true,
    "tempToken": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}

Errors

HTTPcode / i18nKeyReason
400(DTO validation)Invalid email or missing password
401auth.login.invalid_credentialsEmail not found OR password mismatch (deliberately ambiguous)
401auth.login.account_lockedUser.lockedUntil in the future (too many failed attempts)
401auth.login.account_suspendedUser.status = SUSPENDED
401auth.login.account_deactivatedUser.status = DEACTIVATED (use reactivate flow)
403auth.login.email_not_verifiedAccount exists but User.emailVerified = false
429(throttle)Rate limit exceeded (20 req/hour)

Side effects

  1. Lookup User by email.
  2. bcrypt compare against User.passwordHash.
  3. On success: create Session record, generate access + refresh JWTs, set httpOnly cookie.
  4. On failure: increment User.loginAttempts; lock account (User.lockedUntil) after threshold (config auth.lockout_threshold).
  5. If User.twoFactorEnabled: skip token issuance, return tempToken from Challenge table.
  6. Track device via device-tracking.service — new device → security alert email.
  7. Audit log: auth.login.success or auth.login.failure.

Code samples

curl -X POST https://api.bio.re/api/v1/auth/login \
  -H 'Content-Type: application/json' \
  -c cookies.txt \
  -d '{
    "email": "[email protected]",
    "password": "SecureP@ss123",
    "captchaToken": "<turnstile_token>"
  }'
type LoginResponse =
  | { accessToken: string; expiresIn: number }
  | { requiresTwoFactor: true; tempToken: string };

async function login(email: string, password: string, captchaToken?: string): Promise<LoginResponse> {
  const res = await fetch('https://api.bio.re/api/v1/auth/login', {
    method: 'POST',
    credentials: 'include', // accept refresh cookie
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email, password, captchaToken }),
  });
  const json = await res.json();
  if (!res.ok || !json.success) {
    throw Object.assign(new Error(json?.error?.message ?? 'Login failed'), {
      code: json?.error?.code,
    });
  }
  return json.data;
}
import { useMutation } from '@tanstack/react-query';

export function useLogin() {
  return useMutation({
    mutationFn: async (input: { email: string; password: string; captchaToken?: string }) => {
      const res = await fetch('/api/v1/auth/login', {
        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 ?? 'Login failed'), {
          code: json?.error?.code,
          i18nKey: json?.error?.i18nKey,
        });
      }
      return json.data as LoginResponse;
    },
    onSuccess: (data) => {
      if ('requiresTwoFactor' in data) {
        router.push(`/auth/2fa?tempToken=${data.tempToken}`);
      } else {
        // Store accessToken in memory; refresh cookie is httpOnly
        router.push('/dashboard');
      }
    },
  });
}

Try it

POST
/api/v1/auth/login

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

curl -X POST "https://loading/api/v1/auth/login" \  -H "Content-Type: application/json" \  -d '{    "email": "[email protected]",    "password": "SecureP@ss123"  }'
{
  "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"
  }
}
{
  "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.ts119–142
DTO (request)apps/api-core/src/modules/auth/dto/index.ts74–90 (LoginDto)
DTO (response)apps/api-core/src/modules/auth/dto/response.dto.ts17–29 (LoginResponseDto)
Serviceapps/api-core/src/modules/auth/auth.service.tslogin(), device-tracking.service.ts
Prisma modelspackages/prisma/prisma/schema.prismaUser, Session, Challenge, Device

On this page