Workaround for `expires_in` Type Error in Auth.js (formerly NextAuth.js)

May 6, 2024 (2mo ago)

259 views

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.
Intro

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.

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.

넀이버 포럼 μŠ€ν¬λ¦°μƒ·
A screenshot from Naver Developer Forum asking about the OAuth 2.0 spec

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:

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.