BIO.RE
Authentication

Refresh Access Token

Exchange a refresh token (from cookie or body) for a new access + refresh JWT pair. Token rotation enforced.

POST /api/v1/auth/refresh — 🌐 Public · Rate limit: 60 req / hour

Rotates the refresh token. Reads from the biore_refresh httpOnly cookie by default, or from body.refreshToken as fallback. Issues a new access token + new refresh token (cookie). Detects refresh token reuse → revokes ALL sessions for the user (security alert).

Token reuse detection: if a refresh token is presented after it's been rotated, the entire user session family is revoked and a security alert email is sent. Clients must always discard the old refresh token immediately upon receiving a new one.

Request

Cookies

CookieNotes
biore_refreshhttpOnly, Secure, SameSite=Strict, scoped to .bio.re. Auto-sent by browsers; preferred path.
FieldTypeRequiredValidationNotes
refreshTokenstring@IsOptional() @IsString()Used only when cookie is absent (e.g., mobile app non-web client)

Response

200 OKApiResponseOf<LoginResponseDto>

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

A new biore_refresh cookie is set. The old refresh token is revoked (Session record updated).

Errors

HTTPcode / i18nKeyReason
401auth.refresh.invalid_tokenRefresh token not found, expired, or already rotated
401auth.refresh.token_reuse_detectedSecurity alert — token reused after rotation; all user sessions revoked
401auth.refresh.account_suspendedUser.status = SUSPENDED between issuance and refresh
429(throttle)Rate limit exceeded (60 req/hour)

Side effects

  1. Verify refresh token signature + lookup Session record.
  2. Reuse check: if Session.rotatedAt != null, all sessions for this User are revoked + admin alert + email to user (security incident).
  3. On valid refresh: mark old Session.rotatedAt = now(), create new Session with new refresh token, issue new access token.
  4. Update httpOnly cookie with new refresh token.
  5. Audit log: auth.refresh.success or auth.refresh.token_reuse_detected.

Code samples

# Cookie-based (preferred):
curl -X POST https://api.bio.re/api/v1/auth/refresh \
  -b cookies.txt -c cookies.txt

# Body-based (mobile / non-cookie clients):
curl -X POST https://api.bio.re/api/v1/auth/refresh \
  -H 'Content-Type: application/json' \
  -d '{"refreshToken": "..."}'
async function refresh(): Promise<{ accessToken: string; expiresIn: number }> {
  const res = await fetch('https://api.bio.re/api/v1/auth/refresh', {
    method: 'POST',
    credentials: 'include', // sends + receives biore_refresh cookie
  });
  const json = await res.json();
  if (!res.ok || !json.success) {
    throw Object.assign(new Error(json?.error?.message ?? 'Refresh failed'), {
      code: json?.error?.code,
    });
  }
  return json.data;
}
import { useMutation, useQueryClient } from '@tanstack/react-query';

export function useRefresh() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: async () => {
      const res = await fetch('/api/v1/auth/refresh', {
        method: 'POST',
        credentials: 'include',
      });
      const json = await res.json();
      if (!res.ok || !json.success) {
        throw Object.assign(new Error(json?.error?.message ?? 'Refresh failed'), {
          code: json?.error?.code,
        });
      }
      return json.data;
    },
    onError: (err: { code?: string }) => {
      if (err.code === 'auth.refresh.token_reuse_detected') {
        // Security incident — force re-login
        qc.clear();
        router.push('/login?reason=security_alert');
      }
    },
  });
}

Try it

POST
/api/v1/auth/refresh

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

refreshToken?string

Refresh token (optional — prefer httpOnly cookie)

Response Body

application/json

application/json

application/json

curl -X POST "https://loading/api/v1/auth/refresh" \  -H "Content-Type: application/json" \  -d '{}'
{
  "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.ts166–186
DTO (request)apps/api-core/src/modules/auth/dto/index.ts98–101 (RefreshDto)
DTO (response)apps/api-core/src/modules/auth/dto/response.dto.ts17–29 (LoginResponseDto)
Serviceapps/api-core/src/modules/auth/auth.service.tsrefresh(), reuse detection logic
Prisma modelpackages/prisma/prisma/schema.prismaSession.rotatedAt (token rotation tracking)

On this page