Skip to content

[v5] hasNextPage() uses incorrect default for per_page causing infinite loops #1259

@nhsiehgit

Description

@nhsiehgit

Checklist

  • I have looked into the Readme, Examples, and FAQ and have not found a suitable solution or answer.
  • I have looked into the API documentation and have not found a suitable solution or answer.
  • I have searched the issues and have not found a suitable solution or answer.
  • I have searched the Auth0 Community forums and have not found a suitable solution or answer.
  • I agree to the terms within the Auth0 Code of Conduct.

Description

Package

  • Package: auth0 (Node.js SDK)
  • Version: v5.x (Management API client)

Summary

The hasNextPage() function in paginated list methods (e.g., roles.list(), clients.list(), clientGrants.list()) uses a different default value for per_page than the API request, causing infinite loops when per_page is not explicitly provided.

Description

When calling a paginated list method without specifying per_page, the SDK:

  1. Sends the API request with per_page=50 (from destructured defaults)
  2. Evaluates hasNextPage() using request?.per_page ?? 1 (falls back to 1)

This mismatch causes hasNextPage() to incorrectly return true when there are 1-49 items, leading to infinite pagination loops.

Affected Methods

Methods using the page/per_page pagination pattern with this buggy hasNextPage logic:

hasNextPage: (response) => (response?.items ?? []).length >= (request?.per_page ?? 1)

AFFECTED (page/per_page pattern):

  • roles.list()
  • clients.list()
  • organizations.members.roles.list()
  • organizations.enabledConnections.list()
  • organizations.invitations.list()
  • And others using the same pattern...

NOT AFFECTED (cursor-based pattern using from/take with response.next):

  • clientGrants.list() - uses hasNextPage: (response) => response?.next != null
  • connections.list() - uses hasNextPage: (response) => response?.next != null
  • organizations.list() - uses hasNextPage: (response) => response?.next != null
  • organizations.members.list() - uses hasNextPage: (response) => response?.next != null

The cursor-based methods check response.next for pagination, which is a safe pattern that doesn't depend on comparing counts against page size.

Suggested Fix

Option 1: Use the same default in hasNextPage:

hasNextPage: (response) => (response?.roles ?? []).length >= (request?.per_page ?? 50)

Option 2: Capture the effective per_page and use it in the closure:

const effectivePerPage = request?.per_page ?? 50;
// ...
hasNextPage: (response) => (response?.roles ?? []).length >= effectivePerPage

Workaround

Always explicitly pass per_page when calling list methods:

// Works correctly
const page = await client.roles.list({ per_page: 50 });

Environment

  • Node.js: v20.x
  • auth0 SDK: v5.x
  • OS: macOS / Linux

Reproduction

Steps to Reproduce

const { ManagementClient } = require('auth0');

const client = new ManagementClient({
  domain: 'your-domain.auth0.com',
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',
});

// Assume you have 8 roles in your tenant
async function listAllRoles() {
  const allRoles = [];
  let page = await client.roles.list(); // No per_page specified
  
  allRoles.push(...page.data);
  
  // BUG: hasNextPage() checks 8 >= 1 → true (should check 8 >= 50 → false)
  while (page.hasNextPage()) {
    console.log('Fetching next page...'); // This prints forever!
    page = await page.getNextPage();
    allRoles.push(...page.data);
  }
  
  return allRoles;
}

Expected Behavior

hasNextPage() should return false when the number of items returned is less than the effective per_page used in the API request (50 by default).

Actual Behavior

hasNextPage() returns true when items >= 1, causing infinite loops when there are 1-49 items.

Root Cause

In the generated SDK code (e.g., roles/client/Client.js):

// Line 91 - API request uses default of 50
const { per_page: perPage = 50, page = 0, ... } = request;

// Line 161 - hasNextPage uses default of 1
hasNextPage: (response) => {
  return (response?.roles ?? []).length >= (request?.per_page ?? 1);
  //                                        ^^^^^^^^^^^^^^^^^^^^
  //                                        Should use perPage (50), not request?.per_page ?? 1
}

Additional context

Additional Frustration: Inconsistent API Response Formats

During debugging, we discovered that the Auth0 Management API returns different response formats depending on whether pagination query parameters are provided. This behavior is not well-documented and made debugging significantly more difficult.

Without pagination params (GET /api/v2/roles):

[
  { "id": "rol_xxx", "name": "Admin", "description": "..." },
  { "id": "rol_yyy", "name": "Editor", "description": "..." }
]

Returns a plain array of role objects.

With pagination params (GET /api/v2/roles?page=0&per_page=50):

{
  "roles": [...],
  "length": 8,
  "total": 8
}

Returns an object with roles array and pagination metadata.

This inconsistency, combined with the SDK's internal response wrapping (the Page<T> object with .data, .hasNextPage(), .getNextPage()), made it extremely difficult to:

  1. Understand what the raw API was returning
  2. Debug why pagination was behaving unexpectedly
  3. Set up proper mock responses for testing

We spent considerable time investigating whether our wiremock responses were formatted correctly, when the actual issue was the SDK's hasNextPage() logic. Clearer documentation on the API response format variations would help developers avoid this confusion.

node-auth0 version

5

Node.js version

v20.18.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugThis points to a verified bug in the code

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions