Front-end
Next.js 14 Next-auth를 사용한 로그인 구현
ghDev
2024. 7. 23. 16:09
Next-auth란?
Next.js에서 사용하는 인증 라이브러리로 소셜 로그인을 간단하게 구현 할 수 있고
일반적인 백엔드서버와의 로그인 방식(JWT)도 적용이 가능하다.
오늘은 내가 아트인포에 적용한 방식에 대해 얘기해 보려한다.
/api/auth/[...nextauth]/route.ts
const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }
export const authOptions = {
pages: {
signIn: "/auth/sign-in",
signOut: "/auth/sign-in",
error: "/auth/sign-in",
},
session: {
maxAge: 60 * 60 * 24 * 60,
},
secret: process.env.NEXTAUTH_SECRET,
providers: [
CredentialsProvider({
id: "signin-email",
credentials: {
email: { label: "email", type: "text" },
password: { label: "password", type: "password" },
},
async authorize(credentials): Promise<User | null> {
const response = await fetch(
`${process.env.REST_API_BASE_URL}/auths/login/email`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: credentials?.email,
password: credentials?.password,
}),
},
)
const result = await response.json()
if (result.item) {
return {
id: "",
accessToken: result.item.accessToken,
refreshToken: result.item.refreshToken,
accessTokenExpiresIn: result.item.accessTokenExpiresIn,
refreshTokenExpiresIn: result.item.refreshTokenExpiresIn,
}
}
return null
},
}),
CredentialsProvider({
id: "sns",
credentials: {
accessToken: { label: "Access Token", type: "text" },
type: { label: "SNS Type", type: "text" },
},
async authorize(credentials): Promise<User | null> {
if (!credentials?.accessToken || !credentials.type) {
return null
}
try {
const response = await fetch(
`${process.env.REST_API_BASE_URL}/auths/login/sns`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
type: credentials.type,
token: credentials.accessToken,
}),
},
)
if (!response.ok) {
throw new Error("Network response was not ok")
}
const result = await response.json()
if (result.item) {
return {
id: "",
accessToken: result.item.accessToken,
refreshToken: result.item.refreshToken,
accessTokenExpiresIn: result.item.accessTokenExpiresIn,
refreshTokenExpiresIn: result.item.refreshTokenExpiresIn,
}
} else {
console.error("Unexpected API response structure:", result)
return null
}
} catch (error) {
console.error("SNS sign-in error", error)
return null
}
},
}),
],
callbacks: {
async jwt({ token, user }: { token: any; user: User }) {
if (user) {
cookies().set({
name: "accessToken",
value: user.accessToken,
httpOnly: true,
path: "/",
expires: new Date(user.accessTokenExpiresIn),
})
token.accessToken = user?.accessToken
token.refreshToken = user?.refreshToken
token.accessTokenExpiresIn = user?.accessTokenExpiresIn
token.refreshTokenExpiresIn = user?.refreshTokenExpiresIn
}
if (new Date() > new Date(token.accessTokenExpiresIn)) {
const result = await handleRefreshToken({
accessToken: token.accessToken,
refreshToken: token.refreshToken,
})
if (result) {
token.accessToken = result.accessToken
token.refreshToken = result.refreshToken
token.accessTokenExpiresIn = result.accessTokenExpiresIn
token.refreshTokenExpiresIn = result.refreshTokenExpiresIn
}
return token
}
return token
},
async session({ session, token }: any) {
session.token = token
return session
},
},
}
next.js Api 라우트를 사용하여 /api/auth/[...nextauth] 경로에 설정을 해준다.
next-auth는 해당 라우트를 사용해 인증 로직을 처리한다.
- pages, session, secret 등에 기본적인 프로젝트별 설정을 해줘야한다.
- providers에 있는 CredentialProvider의 id 값들이 내가 진행할 로그인 방식의 id값이 된다.
next-auth의 signIn method를 사용해 'signin-email' provider를 호출하고
기본적인 flow는
- provider를 사용해 로그인 성공시 받아온 토큰 정보들을 리턴해주게 되면
- callbacks의 user객체에 담기게 되고 토큰의 유효기간을 확인한뒤 session 객체에 담아준다
유효기간을 확인하는 이유는 로그인 뿐만 아니라 페이지 라우팅이 변경될시(페이지 이동시에도 callbacks가 실행된다) session 객체에 담아주는 이유는 Next-auth에선 해당 session 객체의 데이터를 암호화하여 쿠키에 담을 뿐아니라
CSR 환경에서 useSession 훅으로 쉽게 접근이 가능하다.
소셜 로그인과 같은 경우는 구글을 예를 들자면
- 구글로그인 진행
2. 로그인 성공후 redirect 된 callback 페이지에서 구글에서 받아온 토큰을 signIn 'sns'로 요청한다.
3. 아트인포 백엔드 서버는 받아온 토큰을 구글에 한번더 재 확인 후 백엔드 서버의 토큰을 발급해준다. 이 뒤는 이메일 방식과 같다.
백엔드서버 없이 진행하는 프로젝트라면 좀 더 유용하겠지만 Next-auth는 사실 필수적인 라이브러리는 아니라고 생각한다.
사용한 이유로는 페이지별 로그인 여부 확인 등 인증 부분에선 확실히 강점이 있다고 생각해서 였고 제작년부터 눈여겨 보던
라이브러리라 제대로 사용해보고 싶기도 했다.
다음 프로젝트에선 인증 부분을 직접 구현하여 볼 생각이다.