-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Description
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:
- A Notion public integration
- The following repo: Montreal-Dev/Notion_Refresh_Token_Error
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