-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ Feat ] 인증 시스템 초기 세팅 및 login api 적용 #178
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고생하셨습니다 !!
1next-auth
를 통한 인증/인가 구현.. 사실 사용해본 적이 한번도 없어서 신기하네요 ! next
에서 인증 인가를 효율적으로 수행하고, 세션을 관리할 수 있다... 배우고 갑니다 !!
startTransition(async () => { | ||
await loginAction(values); | ||
await session.update(await getSession()); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
startTransition
은 리액트의 클라이언트 컴포넌트에서 상태 업데이트의 우선순위를 낮추고, 다른 우선순위가 높은 긴급한 상태를 업데이트하기 위해 사용하는 것으로 알고 있어요 !
특히나 리액트의 상태 업데이트가 아닌 서버 액션과 세션을 해당 함수 안에서 업데이트하는 것이 의미가 있을 지 잘 모르겠는데 어떻게 생각하시나용 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UI가 일시적으로 멈춰보이는 것은, 비동기 작업이기 때문에 어쩔 수 없는 것이라고 생각해요 !
리액트의 동시성 렌더링이 사용되어야 하는 경우는 렌더링을 최적화하기 위해 네트워크마다 상이한 딜레이 타임을 가지게 하면서 어느 정도 유연하게 UI를 블로킹하면서 이전의 지연된 상태의 UI를 보여주려고 할 때 사용하는 것이 맞다구 알고있습니다 !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
꼼꼼히 정리해주셔서 저도 공부하는데 너무 수월했네요!
고생 많으셨습니다! 저도 이번 기회에 NextAuth
에 대해 많이 찾아봤는데 토론을 나눌 기회가 있으면 좋겠네요!
src/api/user/actions.ts
Outdated
} catch (error) { | ||
if (error instanceof AuthError) { | ||
switch (error.type) { | ||
case "CredentialsSignin": { | ||
return { error: "Invalid credentials!" }; | ||
} | ||
default: { | ||
return { error: "Something went wrong!" }; | ||
} | ||
} | ||
} | ||
|
||
throw error; // AuthError가 아닐 경우 다른 try catch로 보내주기 위함 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저희 서비스는 에러 메시지를 페이지 이동 없이 회원가입 또는 로그인 페이지에서 그대로 출력하기 때문에 useFormState
훅에 서버 액션을 연결해서 처리하는게 더 좋아보이는데 어떻게 생각하시나요?
src/middleware.ts
Outdated
if (isLoggedIn) { | ||
return NextResponse.redirect( | ||
new URL(DEFAULT_LOGIN_REDIRECT, req.nextUrl), | ||
); | ||
} | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.min(6) | ||
.max(12) | ||
.regex(/^[a-zA-Z0-9]+$/), | ||
email: z.string().min(6).max(18), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분 왜 삭제하셨는지 이유가 궁금합니다!
#️⃣ Related Issue
Closes #175
✅ Done Task
☀️ New-insight
자세한 코드 설명은 PR Point에서 진행하고, New-insight는 개념 정리 위주로 작성합니다.
NextAuth.js
next 공식에서도 추천하는 인증/인가 라이브러리입니다. 대체제로는 lucia라는 라이브러리가 있습니다. lucia가 좀 더 경량으로 구성돼 있고 인증 커스터마이징 유연성이 더 높은 대신, 초기 설정은 nextAuth가 더 간결하다고 합니다.
인증 로직을 클라이언트에서
react-query
,local storage
등을 사용하여 처음부터 구현하는 대신NextAuth
를 사용하여 로그인, 회원가입, 세션 관리, OAuth 등을 쉽게 설정할 수 있습니다. 특히 인증/인가 관련 코드를 프론트에서 분리하여server action
으로 관리할 수 있어 손쉬운 구현, 보안, 유지 보수와 확장성에 용이하다는 장점이 있습니다.세션 데이터 관리 방식은
/src/auth.ts
에서session: { strategy: "jwt" | "database" }
로 정할 수 있는데,jwt
를 선택하면 세션 데이터를 암호화된jwt
로 만들어서 클라이언트의 쿠키에 저장합니다. 클라이언트에서 세션 데이터를 얻으려면 이 쿠키에 저장된 세션 토큰을 서버로 보내 검증 및 해석하여 데이터를 반환 받는 방식입니다. 이 때 토큰의 암호화를 위해.env
에AUTH_SECRET
이 있어야 하는데npx auth secret
명령어를 통해 자동으로 생성 가능합니다.database
를 선택하면 세션 데이터를 데이터베이스에 저장하고 관리합니다. jwt때와는 달리 클라이언트의 쿠키에는 세션 ID만 저장합니다. 클라이언트에서 세션 데이터를 얻으려면 이 세션 ID를 next 서버로 보내고, DB에서 ID를 조회하여 데이터를 반환 받는 방식입니다.NextAuth 사용 시 인증 흐름도는 보편적으로 다음과 같습니다.
/api/auth/[...nextauth]
라는 API 라우트로 전달nextAuth가 세션 데이터를 jwt로 생성하고 클라이언트의 쿠키에 저장 (세션 데이터에는 로그인 정보, 유저 정보, 토큰 등등 아무거나 넣을 수 있으며, 아래 세션 데이터 얻는 방법을 통해 컴포넌트에서 사용할 수 있음)
정해진 경로로 리다이렉트 및 세션 데이터 확인 가능
인가를 받을 때는 서버/클라이언트 컴포넌트에 따라 다른 방식으로 session 데이터를 얻어와 그 안의 accessToken을 추출하여
await kyInstance.get("api/user", { headers: { Authorization: Bearer ${accessToken}, }, })
처럼 사용하면 됩니다.await auth()
)useSession
)useSession
을 사용하려면RootLayout
에서SessionProvider
로 감싸야 합니다.login
요청으로 응답받은accessToken
으로 user data를 얻어서 세션 데이터에 추가함)미들웨어(middleware.ts)
클라이언트에서 Next.js서버로 보내는 모든 요청을 가로채서 권한에 따른 리다이렉트, 요청 헤더 수정 등의 역할을 하는 next.js 특수 파일입니다. 프로젝트 최상단에 위치시키거나 src 경로 사용 시에는
/src
에 middleware.ts 파일명으로 작성합니다.NextAuth
사용 시auth()
함수를 사용하여 미들웨어 함수를 구성하면 세션 데이터가 요청(request) 객체에 추가되는데 이걸로 로그인 여부에 따라 특정 라우트에 접근하지 못하도록protected routes
를 설정할 수 있고, 특정 페이지(로그인 페이지)로 리다이렉트 하도록 구현할 수 있습니다.이런 인증 여부나 역할 검사 뿐만 아니라 request 객체에 들어있는 여러 정보를 가지고 국가/지역별 접근 차단, 로그 분석, API 호출 횟수 제한 등도 구현 가능합니다.
미들웨어는
node.js
환경을 경량화해서 만든edge network
환경에서 실행되므로node.js
의 fs 모듈 같은 것은 사용할 수 없고, 요청을 가로채는 기능이므로 함수의 실행 시간이 길어지는 로직이 포함되지 않도록 조심해야 합니다.세션 데이터
auth.config.ts
에 작성한 인증 로직이 반환하는 데이터를auth.ts
의callbacks
를 거쳐가며 데이터를 연결해주는 방식입니다.authorize
→jwt
→session
함수 순서로 데이터가 이어지며 마지막 순서인session
함수의 반환값이 미들웨어나 컴포넌트에서 사용할 세션 데이터 입니다.💎 PR Point
파일 변경 순서대로 정리하겠습니다. 코드와 함께 보시는 것을 추천 드립니다!
next-auth.d.ts
src/auth.ts
에서 정의한 session과 token 매개변수의 타입을 확장하는 용도의 type 파일입니다. 로그인 api 결과로 받는accessToken
을 추가했습니다.jwt
는next-auth/jwt
로 따로 써야 작동하길래 따로 작성 했습니다.src/api/user/actions.ts
NextAuth
로 만든signIn
함수를 사용하는server action
함수입니다. 첫 인자에는 signIn 방식(OAuth
플랫폼 종류 혹은 사용자 지정 인증 방식인credentials
)을 넣고 두번째 인자에는 signIn에 필요한 데이터와 옵션을 넣어줍니다.server action
이니 "use server";를 잊지 않도록 조심합시다.server action
함수는 각 폼의handleSubmit
함수에서 호출하면 됩니다.여기서 startTransition은 비동기 작업이 진행되는 동안(특히 느린 네트워크 환경일 때)에도 UI가 반응성을 유지하고 사용자가 상호 작용할 수 있는 환경을 제공할 수 있습니다. 사용하지 않으면 비동기 작업이 진행될 때 UI가 잠시 멈출 수 있어 UX에 영향을 줄 수 있습니다. 여러 프로젝트를 확인해본 결과 폼의
handleSubmit
에는 꼭 쓰는 것이 국룰인 것 같습니다.src/app/api/auth/[...nextauth]/route.ts
컴포넌트가
await auth()
나useSession
으로 요청하는 세션 데이터는 기본적으로 이 API 라우트를 통해 가져옵니다. 그래서 파일 내용도NextAuth
가 export하는handlers
뿐입니다. 추상화가 잘 돼있어 간결한 코드로 구현이 가능한 것이 좋은 것 같습니다.RootLayout
react query
용provider
와 클라이언트 컴포넌트에서 Session 데이터를 가져올 때 사용하는SessionProvider
가 추가됐습니다. 그리고 공통 헤더에는 로그인 여부를 알 수 있도록 session을 props로 받게 변경했습니다.주의할 점은
SessionProvider
가re-rendering
되지 않는RootLayout
에서 사용되기 때문에 session을 직접 업데이트 해 주지 않으면 새로고침 전 까지useSession
으로 얻어온 세션 데이터는 처음 세션 데이터를 사용하게 되니 세션 데이터 변경이 필요한 경우 직접 업데이트를 해 주어야 합니다. 이 상황의 예를 들면 login 후 다음 페이지인 /user로 갔을 때useSession
은 여전히 빈 세션을 계속 사용하게 됩니다. 그에 따라 위의handleSubmit
예시처럼 login action 이후SessionProvider
를 명시적으로 업데이트하는 로직이 필요합니다.src/auth.ts
src/auth.config.ts
server action
으로 사용할 로그인, 로그아웃 함수 및 세션 데이터 핸들러 함수를 제공하는 부분입니다.OAuth
의 경우 해당 플랫폼의 clientId와 clientSecret을 얻어다가 env파일을 통해 넣어주면 작동합니다.auth
를 두 파일로 나눈 이유는 관심사 분리의 일환입니다.middleware.ts
파일 맨 아래
config
의matcher
부터 설명하자면, 이 미들웨어를 호출할 route를 설정하는 부분입니다. Next.js의 내부 파일과 정적 파일은 미들웨어를 호출하지 않고,/api
나/trpc
로 시작하는 건 미들웨어를 호출하도록 합니다.기본적으로는
export function middleware(req) { ... }
이런 식으로 미들웨어 함수를 정의하지만,NextAuth
의auth
함수를 통해 req 매개변수에 인증 정보(auth)를 포함시켜서 미들웨어 함수를 정의할 수 있습니다.현재 로직을 설명하자면, 먼저
/api
로 시작하는 요청은 별다른 처리 없이 넘깁니다. 앞서 설명한 matcher와 상충되는 로직이긴 하나 추후에 api 요청 객체를 조작할 일이 있을 것 같아 남겨두었습니다.다음으로는 로그인 없이 접근할 수 있는 경로인지 확인하여 만약 로그인 된 상태라면
DEFAULT_LOGIN_REDIRECT
인 /user 페이지로 리다이렉트 시킵니다. 그게 아니라면 별다른 처리 없이 넘깁니다.마지막으로 로그인도 안했고 공용 접근 경로(/group)도 아니라면 로그인하라고 하기 위해 /login으로 리다이렉트 시킵니다.
추후에 이 리다이렉트 로직(미들웨어 사용법)은 많은 논의와 변경이 필요할 것 같으니 꼭 숙지해주셨으면 합니다.
📸 Screenshot