Post

Next-auth을 통한 일관된 인증 상태 관리

Next-auth을 통한 일관된 인증 상태 관리

문제

기존에 했던 프로젝트에서 next.js에서 지원하는 cookies를 사용해서 로그인을 구현했습니다. 하지만 로그인 직후의 client측에서 상태를 변화하는 것을 감지해야 client 측에서 로그인 상태에 따른 화면을 재랜더링을 해야할 필요가 있었고, 또한 로그인 관련 로직을 여러갈래로 복잡하게 연결되어있던 부분을 한번에 관리하기 위해서 Next-auth를 사용하기로 결정했습니다.

해결

next-auth에서는 v4 버전과 v5 버전이 있는데 app router를 자주 사용하는 저는 app router 기준으로 업데이트된 v5 버전을 사용하기로 정했습니다.

npm install next-auth@beta로 v5 버전을 설치하고 npx auth secret을 통해 Next.js의 규칙을 준수하여 .env파일에 AUTH_SECRET을 추가해줍니다.

auth.ts파일을 생성해서

1
2
3
4
5
6
// auth.ts
import NextAuth from "next-auth"
 
export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [],
})

해당 코드를 입력하여 Next-auth의 기본세팅을 끝냈습니다.

같이 달램의 로그인은 소셜로그인이 없이 기본 form으로 진행되기 떄문에 Next-auth에서 providers에 Credentials을 추가해서 로그인을 구현할 수 있었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
providers: [
    Credential({
        credentials: {
            email: {},
            password: {},
        },
        authorize: async (credentials) => {
            const { email, password } = credentials

            const response = await fetch(
                `API주소`,
                {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                },
                body: JSON.stringify({
                    email,
                    password,
                }),
                },
            )

            if (!response.ok) {
                const errorResponse: IErrorResponse = await response.json()
                throw new Error(errorResponse.message)
            }

            const data = await response.json()

            return {
                ...data,
                accessToken: data.token,
            }
        },
    }),
],

이렇게 Credential을 통해 email폼과 password폼을 전달받고 fetch로 해당 api와 연결하여 accessToken을 받아서 next.js의 session에서 관리할 수 있었습니다.

로그인을 할 경우 server action으로 전달하거나 client에서 전달할 수 있습니다.

1
2
3
4
5
6
7
// server action으로 전달할 경우
<form
    action={async (formData) => {
        "use server"
        await signIn("credentials", formData)
    }}
>
1
2
3
const credentialsAction = (formData: FormData) => {
    signIn("credentials", formData)
}

이렇게 하면 간단하게 로그인을 구현하고 한번에 next-auth의 providers을 이용해서 로그인 로직을 한번에 관리가 가능하고 여러가지 많은 설정을 할 수 있었습니다.

Next-auth에서 로그인 세션정보을 가져올경우 next-auth에서 해당 auth.ts 파일을 통해서 간단하게 정보를 가져올수도 있고 client 측에서는 자동으로 재랜더링을 해주기 때문에 더욱 간단하게 관리를 할 수 있게 되었습니다.

1
2
3
4
5
6
// 서버사이드에서 로그인 세션
export default async function UserAvatar() {
  const session = await auth()
 
  if (!session?.user) return null
  ...
1
2
3
4
5
6
// 클라이언트 사이드에서 로그인 세션
"use client"
import { useSession } from "next-auth/react"
 
export default function Dashboard() {
  const { data: session } = useSession()

클라이언트 사이드에서 로그인 정보를 가져올려면 useSession을 사용해야하고 그전에 RootLayout에 <SessionProvider>을 감싸줘야합니다.

옵션을 통해 세션의 만료시간과 jwt 사용 여부, callback 등 여러가지 설정 또한 해줄수 있었습니다.

자세한것은 next-auth 공식 문서여기서 확인 할 수 있었습니다.

저는 이렇게 auth.ts을 관리하여 로그인 로직을 관리하였습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import NextAuth from "next-auth"
import Credential from "next-auth/providers/credentials"

import { IErrorResponse } from "@/types/mypage/mypage"

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [
    Credential({
      credentials: {
        email: {},
        password: {},
      },
      authorize: async (credentials) => {
        const { email, password } = credentials

        const response = await fetch(
          `API주소`,
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              email,
              password,
            }),
          },
        )

        if (!response.ok) {
          const errorResponse: IErrorResponse = await response.json()
          throw new Error(errorResponse.message)
        }

        const data = await response.json()

        return {
          ...data,
          accessToken: data.token,
        }
      },
    }),
  ],
  session: {
    strategy: "jwt",
    maxAge: 60 * 60,
  },
  callbacks: {
    signIn: async () => {
      return true
    },
    jwt: async ({ token, user }) => {
      const newToken = { ...token }
      if (user?.accessToken) {
        newToken.accessToken = user.accessToken
      }
      return newToken
    },
    session: async ({ session, token }) => {
      const newSession = { ...session }
      if (token?.accessToken) {
        newSession.accessToken = token.accessToken
      }
      return newSession
    },
    authorized: async ({ auth }) => {
      return !!auth
    },
  },
})

후기

next-auth는 기본 form을 제외하고도 페이스북, 구글, 깃헙, 카카오등 여러가지 OAuth또한 다 지원합니다.

세션과 토큰 등 여러가지를 자동으로 관리해주고 보안 또한 CSRF 보호, 암호화된 JWT 토큰, 서버 세션 저장 등 보안에 필요한 설정을 기본적으로 제공해줍니다.

다양한 콜백을 지원해서 유연성 또한 갖추어있기 때문에 Next.js을 사용 할 경우 직접 구현도 좋지만 Next-auth를 고려해보는것도 나쁘지 않은것 같았던것 같습니다.

This post is licensed under CC BY 4.0 by the author.