Skip to content

🐞 Notion provider: OAuth response includes "refresh_token: null", causing CallbackRouteError #13109

@Montreal-Dev

Description

@Montreal-Dev

Provider type

Notion

Environment

  System:
    OS: macOS 15.5
    CPU: (8) arm64 Apple M2
    Memory: 139.14 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 23.5.0 - /opt/homebrew/bin/node
    npm: 11.0.0 - /opt/homebrew/bin/npm
    pnpm: 9.15.1 - /opt/homebrew/bin/pnpm
    bun: 1.1.45 - ~/.bun/bin/bun
  Browsers:
    Brave Browser: 137.1.79.119
    Safari: 18.5
  npmPackages:
    next: 15.3.2 => 15.3.2 
    next-auth: 5.0.0-beta.28 => 5.0.0-beta.28 
    react: ^19.1.0 => 19.1.0 

Reproduction URL

https://github.com/Montreal-Dev/Notion_Refresh_Token_Error

Describe the issue

When authenticating with the Notion provider,
The OAuth response includes:

"refresh_token": null

This causes @auth/core (Auth.js v5) to throw a CallbackRouteError due to type check inside OAuth4WebApi

if (typeof body.refresh_token !== 'string') {
  throw new OperationProcessingError('"response" body "refresh_token" property must be a string');
}

📖 Source: src/index.ts#L3481-L3485 (panva/oauth4webapi)

This is expected behavior from Notion.
According to their documentation:

The tokens need to be exchanged the first time a user attempts to add your Link Preview enabled URL to a page. After the initial exchange, the Notion access_token is long-living and doesn’t need to be updated. If you prefer, Notion can also support refresh tokens and fetch new tokens from your service.

📖 Source: Notion Developer Docs – Set up the authorization flow


Error :

[auth][error] CallbackRouteError: Read more at https://errors.authjs.dev#callbackrouteerror
[auth][cause]: OperationProcessingError: "response" body "refresh_token" property must be a string

Stack Trace :

at OPE (oauth4webapi/build/index.js:214:12)
at assertString (oauth4webapi/build/index.js:348:19)
at processGenericAccessTokenResponse (oauth4webapi/build/index.js:1284:9)
at processAuthorizationCodeOAuth2Response (oauth4webapi/build/index.js:1496:20)
at handleOAuth (@auth/core/lib/actions/callback/oauth/callback.js:173:35)
at callback (@auth/core/lib/actions/callback/index.js:47:41)
at AuthInternal (@auth/core/lib/index.js:43:24)
at Auth (@auth/core/index.js:130:34)

Response Body :

🔒 **** indicate redacted values for privacy.

{
  "access_token": "ntn_***",
  "token_type": "bearer",
  "refresh_token": null,
  "bot_id": "****",
  "workspace_name": "****",
  "workspace_icon": "****",
  "workspace_id": "****",
  "owner": {
    "type": "user",
    "user": {
      "id": "****",
      "name": "****",
      "avatar_url": "****",
      "person": {
        "email": "****"
      }
    }
  },
  "duplicated_template_id": null,
  "request_id": "****"
}

How to reproduce


You’ll need:

Copy the example environment file & add your credentials:

cp .env.local.example .env.local

Then fill in:

AUTH_NOTION_ID=your_client_id
AUTH_NOTION_SECRET=your_client_secret
AUTH_NOTION_REDIRECT_URI=http://localhost:3000/api/auth/callback/notion

Install dependencies and run the dev server:

pnpm install
pnpm dev

Open http://localhost:3000/ and click "Sign in with Notion".


You’ll encounter a CallbackRouteError due to refresh_token: null in Notion’s response, which is valid in Notion, but breaks @auth/core.

Expected behavior


Notion & providers with same behaviour may need to conform their token responses.

✅ Suggested Fix:
Auth.js has a token.conform, which can be used to normalize token responses.

Here's an example fix:

token: {
  url: "https://api.notion.com/v1/oauth/token",
  conform: async (response: NextResponse) => {
    const data = await response.clone().json();
    if (data?.refresh_token === null) {
      delete data.refresh_token
    }
    return new Response(JSON.stringify(data), {
      status: response.status,
      headers: response.headers,
    });
  },
},

Here is an example of a Notion provider fix

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingproviderstriageUnseen or unconfirmed by a maintainer yet. Provide extra information in the meantime.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions