Auth.js (๊ตฌ NextAuth.js) ์ฌ์ฉ ์ ๋ฐ์ํ๋ `expires_in` ํ์ ์๋ฌ๋ฅผ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ์๊ฐํฉ๋๋ค.
TLDR
์ forked ๋ฒ์ ์ oauth4webapi๋ฅผ ์ฌ์ฉํ์๋ฉด, `expires_in`์ด string ํ์ ์ด์ด๋ ์ฒ๋ฆฌํด์ค๋๋ค. forked ๋ฒ์ ์ @jacobkim/oauth4webapi์์ ํ์ธํ์ค ์ ์์ต๋๋ค.
์ด์ ์ค๋ช
๋ค์ด๋ฒ์ Azure AD B2C ํ๋ก๋ฐ์ด๋๋ฅผ ์ฌ์ฉํ๋ ค ํ์๋ ๋ค์๊ณผ ๊ฐ์ ์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค.
[auth][cause]: OperationProcessingError: "response" body "expires_in" property must be a positive number
์๊ณ ๋ณด๋ ๋ค์ด๋ฒ ์ธก์์ OAuth 2.0 ์คํ์ ์ค์ํ์ง ์๊ณ , expires_in
๋ณ์๋ฅผ number ํ์
์ด ์๋, string ์ผ๋ก ๋ฐํํ๊ณ ์์์ต๋๋ค.
Auth.js๋ ๋ด๋ถ์ ์ผ๋ก oauth4webapi ์ด๋ผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ OAuth 2.0 ์ธ์ฆ์ ์งํํฉ๋๋ค. ํ์ง๋ง ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ OAuth 2.0 ์คํ์ ์๊ฒฉํ ์งํค๊ธฐ์, expires_in
์ ์๋์ผ๋ก number type์ผ๋ก ๋ณํํด์ค๋ค๊ฑฐ๋ ํ์ง ์์ต๋๋ค. ์ด๋ฏธ ๋ช๋ฒ ๋ค๋ฅธ ๋ถ๋ค์ด expires_in
์ ์๋์ผ๋ก number ํ์
์ผ๋ก ๋ณ๊ฒฝํ๋ PR์ ์ฌ๋ ธ์ง๋ง, ๋ชจ๋ ๊ฑฐ์ ๋นํ์ต๋๋ค.
- 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
์ ๋ OAuth 2.0 ์คํ์ ์ฒ ์ ํ ์ค์ํ๋ oauth4webapi ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ฐ๋ฐ์์ ์๊ฒฌ์ ์์ ํ ์กด์คํฉ๋๋ค. ํ์ง๋ง ๋ค์ด๋ฒ ์ธก ์์๋ ์ฌ์ด๋ ์ดํํธ ๋๋ฌธ์ expires_in
์ ๋๋ฒ ํ์
์ผ๋ก ๋ณ๊ฒฝํ ์๊ฐ์ด ์์ด ๋ณด์์ต๋๋ค.
ํด๊ฒฐ๋ฐฉ์
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ ๋๊ฐ์ง ๋ฐฉ๋ฒ์ด ์์ต๋๋ค. ๋น์ ์๊ฒ ๊ฐ์ฅ ์ ํฉํ ๋ฐฉ๋ฒ์ ์ ํํ์๋ฉด ๋ฉ๋๋ค.
ํด๊ฒฐ๋ฐฉ์ #1 (์ถ์ฒ)
๋ง์ฝ ์ forked ๋ฒ์ ์ oauth4webapi๋ฅผ ์ฌ์ฉํด๋ ๊ด์ฐฎ๋ค๋ฉด, ์๋ npm ํจํค์ง๋ฅผ ์ฌ์ฉํ์๋ฉด ๋ฉ๋๋ค. ์ forked ๋ฒ์ ์ `expires_in`์ด string ํ์ ์ด์ด๋ ์ฒ๋ฆฌํ๋ฉฐ, forked ๋ ํฌ๋ @jacobhjkim/oauth4webapi์์ ํ์ธํ์ค ์ ์์ต๋๋ค.
NPM์ overrides ๊ธฐ๋ฅ์ ํ์ฉํ๋ฉด, oauth4webapi ํจํค์ง๋ฅผ ๊ฐ๋จํ๊ฒ ๋ฎ์ด์ธ ์ ์์ต๋๋ค.
// package.json
...
"overrides": {
"oauth4webapi": "npm:@jacobkim/oauth4webapi@^2.10.4"
}
๋ง์ฝ ์ ์ฒ๋ผ pnpm์ ์ฌ์ฉํ์ ๋ค๋ฉด, ์๋์ ๊ฐ์ด overrides
๊ฐ ์๋ pnpm.overrides
๋ฅผ ์ฌ์ฉํ์
์ผ ํฉ๋๋ค. (์๊ทธ๋ฌ๋ฉด ์ ์ฒ๋ผ ํ์๊ฐ๋์ pnpm ๊นํ ๋ ํฌ์์ ์ด์๋ค์ ๊ฒ์ํ๊ณ ์์๊ฒ๋๋ค.)
// package.json
...
"pnpm": {
"overrides": {
"oauth4webapi": "npm:@jacobkim/oauth4webapi@^2.10.4"
}
}
npm์ overrides๊ฐ ์ ์๋ ์๋ ์๋ค๊ณ ํฉ๋๋ค.
npm CLI
๋ overrides ๊ธฐ๋ฅ์ด ์ ๋์ํ์ง ์๋๋ค๋ ์ ๋ณด๋ค์ด ์์ต๋๋ค. ๊ด๋ จ ๊นํ ์ด์๋ค ์
๋๋ค:
- bug: overrides property only honored when running install the first time #5850
- [BUG] Overrides are not updating after running npm install #5443
๊ทธ๋ฌ๋ ์์ง๋ npm์ ์ฌ์ฉํ๊ณ ๊ณ์๋ค๋ฉด, pnpm์ ์ฌ์ฉํ์๋๊ฑธ ์ถ์ฒํฉ๋๋ค. pnpm์ด ๋ค๋ฐฉ๋ฉด์์ ํจ์ฌ ์ข์ต๋๋ค.
ํด๊ฒฐ๋ฐฉ์ #2
๋ง์ฝ ๋น์ ์ด ํจํค์ง ์ค๋ฒ๋ผ์ด๋ฉ์ ํ๊ณ ์ถ์ง ์์ผ์๋ค๊ฑฐ๋, ์ fork๋ฅผ ์ฌ์ฉํ๊ธฐ ๊ป๋ฆ์นํ์๋ค๋ฉด ๋ค๋ฅธ ํด๊ฒฐ๋ฐฉ์์ด ์์ต๋๋ค.
์ฌ๊ธฐ์ @numman-ali๋์ด ์ ์ํ๋ฏ, ์ปค์คํ ์ธํฐ์ ํฐ๋ฅผ ์์ฑํ ์๋ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ํน์ provider๋ ์ด ์ธํฐ์ ํฐ๊ฐ OAuth request๋ฅผ ํธ๋ค๋ง ํ๋๊ฑฐ์ฃ .
// 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)
}
๊ฐ์ธ์ ์ผ๋ก๋ overrides ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋๊ฒ์ด, ์ธํฐ์ ํฐ ๋ฐฉ์๋ณด๋ค ์ฝ๊ณ ๋ ๋จธ๋ฆฌ๊ฐ ์ํ๊ฒ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค.