Here is how to fix an Auth.js (formerly NextAuth.js) error that happens when your OAuth 2.0 provider does not follow OAuth 2.0 spec.
TLDR
You can use my forked version of oauth4webapi which automatically converts `expires_in` field from string to number. You can check the forked version at @jacobkim/oauth4webapi.
Issue Description
Here is the error I faced when I tried to integrate Azure AD B2C and Naver provider.
[auth][cause]: OperationProcessingError: "response" body "expires_in" property must be a positive number
Apparently Naver and Azure AD B2C does not respect OAuth 2.0 spec and returns expires_in
parameter as a string not number.
Auth.js uses oauth4webapi internally to handle OAuth 2.0 authentication. However, oauth4webapi strictly follows the OAuth 2.0 spec. It does not automatically convert expires_in
from string to number. Other developers have tried to submit PRs to automatically convert expires_in
from string to number, but all of them were rejected.
- add convertStringExpiresInToNumber function for providers whose response's expires_in is a string instead of an integer(number) #112
- fix: Validate 'expires_in' as a convertible number #77
- allow expires_in string type #47
I respect the author of oauth4webapi's decision to absolutely respect the OAuth 2.0 spec. But I could not wait for Naver and Azure AD B2C to update their API so that expires_in
is returned as a number.
Solution
Here are two solutions to this issue. You can choose the one that fits your needs the best.
Solution #1 (Recommended)
If you are okay with using my forked version of oauth4webapi, you can use my forked version of oauth4webapi which automatically converts `expires_in` field from string to number. You can check the forked repo at @jacobhjkim/oauth4webapi.
We can use NPM's overrides feature to easily override oauth4webapi package.
// package.json
...
"overrides": {
"oauth4webapi": "npm:@jacobkim/oauth4webapi@^2.10.4"
}
If you use pnpm as I do, you must use pnpm.overrides
instead of just overrides
, as shown bellow. (or else you might lose an hour pulling out your hair and searching through GitHub issues.)
// package.json
...
"pnpm": {
"overrides": {
"oauth4webapi": "npm:@jacobkim/oauth4webapi@^2.10.4"
}
}
npm might not honor overrides property
npm CLI
seems to have an issue with the overrides property. Here are some related GitHub issues:
- bug: overrides property only honored when running install the first time #5850
- [BUG] Overrides are not updating after running npm install #5443
If you are still using npm, I recommend switching to pnpm, which is better in many ways.
Solution #2
If you are against overriding your package's dependency or don't want to use my forked package, that's perfectly fine too.
Like @numman-ali suggests here you can write a custom interceptor that intercepts request just for specific OAuth provider.
// app/api/auth/[...nextauth]/route.ts
import { type NextRequest, NextResponse } from "next/server";
import {
auth,
GET as AuthGET,
instagramFetchInterceptor,
POST as AuthPOST,
} from "@/auth"
const originalFetch = fetch
export async function POST(req: NextRequest) {
return await AuthPOST(req)
}
export async function GET(req: NextRequest) {
const url = new URL(req.url)
if (url.pathname === "/api/auth/callback/naver") {
const session = await auth()
if (!session?.user) {
/* Prevent user creation for instagram access token */
const signInUrl = new URL("/?modal=sign-in", req.url)
return NextResponse.redirect(signInUrl)
}
/* Intercept the fetch request to patch access_token request to be oauth compliant */
global.fetch = naverFetchInterceptor(originalFetch) <- your custom interceptor
const response = await AuthGET(req)
global.fetch = originalFetch
return response
}
return await AuthGET(req)
}
Personally, I think utilizing overrides is easier and causes less hassle overall than the interceptor method.