
NextAuth.js 를 이용한 자체 로그인 구현
웹 애플리케이션을 개발하면서 사용자 인증은 핵심적인 요소 중 하나입니다. 이 글에서는 Next.js 애플리케이션에서 NextAuth.js를 활용하여 간편하게 자체 로그인을 구현하고 로그인이 필요한 페이지에서 세션을 효과적으로 관리하는 방법에 대해 알아보겠습니다.
NextAuth.js
NextAuth.js는 Next.js 애플리케이션에서 사용자 인증을 손쉽게 구현할 수 있도록 도와주는 라이브러리입니다. 다양한 인증 공급자(Providers)를 지원하며, 자체 로그인 또한 구현할 수 있도록 도와줍니다.주로 웹 애플리케이션 개발에서 사용되며, 다음과 같은 주요 특징을 제공합니다:
-
다양한 인증 공급자 지원: NextAuth.js는 다양한 인증 공급자(예: Google, Facebook, GitHub, Twitter 등)와 함께 사용할 수 있습니다. 이를 통해 사용자는 웹 애플리케이션에 다양한 방법으로 로그인하거나 가입할 수 있습니다.
-
세션 관리: NextAuth.js는 사용자 세션을 관리하고 보안적으로 유지합니다. 사용자 로그인 상태를 추적하고 세션을 관리하여 애플리케이션 내에서 사용자 인증을 유지합니다.
-
간단한 설정: NextAuth.js를 설정하는 것은 상대적으로 간단하며, 대부분의 설정은 설정 파일을 통해 수행됩니다. 이를 통해 빠르게 인증 시스템을 설정할 수 있습니다.
-
확장성: NextAuth.js는 확장 가능한 아키텍처를 제공하여 사용자 지정 로직 및 필요한 기능을 추가하거나 수정할 수 있습니다.
-
TypeScript 지원: TypeScript를 사용하여 NextAuth.js를 구현할 수 있으며, 타입 안정성을 확보할 수 있습니다.
NextAuth.js는 Next.js와의 통합이 원활하며, Next.js 애플리케이션 내에서 간편하게 사용할 수 있습니다. 이를 통해 웹 애플리케이션에서 사용자 인증 및 세션 관리를 구현하는 과정을 간단하게 만들어줍니다.
NextAuth.js 설정
먼저 NextAuth.js를 설정하고 사용자 정의 로그인 프로세스를 구현하기 위해 pages/api/auth/[...nextauth].js 파일을 만듭니다. 이 파일은 사용자 인증 관련 엔드포인트를 정의합니다. 설정 파일에서는 다음과 같이 NextAuth.js를 초기화합니다.
app directory인 경우 의 파일 구조 https://codevoweb.com/setup-and-use-nextauth-in-nextjs-13-app-directory/ 그러나 next13은 app directory 와 pages directory 를 함께 사용하는것을 지원하기 때문에 app directory를 사용할 경우에도
pages/api/auth/[...nextauth].js로 설정이 가능합니다.
import NextAuth from 'next-auth'; import Providers from 'next-auth/providers'; import axios from 'axios'; export default NextAuth({ providers: [ Providers.Credentials({ // 이 부분은 자체 로그인 로직을 구현합니다. credentials: { username: { label: 'Username', type: 'text' }, password: { label: 'Password', type: 'password' } }, async authorize(credentials) { // 외부 서버와 통신하여 유저 정보와 토큰을 가져오는 로직을 여기에 구현합니다. const { username, password } = credentials; // 외부 서버와의 통신을 통해 유저 정보와 토큰을 가져옵니다. const response = await axios.post('https://your-external-server.com/api/login', { username, password }); const data = response.data; if (data) { // 유저 정보와 토큰을 NextAuth.js 세션에 저장합니다. return { name: data.name, email: data.email, token: data.token }; } else { // 로그인 실패 시 null을 반환합니다. return null; } } }) ], callbacks: { async session(session, token) { // 세션에 토큰 정보를 추가합니다. session.token = token.token; return session; } }, session: { jwt: true } });
자체 로그인 구현
Credentials Provider를 사용하여 자체 로그인 구현. 클라이언트에서 입력한 자격 증명을 서버로 전송하고 토큰을 받아옵니다. credentials 속성은 사용자가 로그인할 때 필요한 자격 증명(예: 사용자 이름과 비밀번호)을 정의하는 데 사용됩니다.
여기에서 credentials 객체의 역할을 자세히 설명합니다:
username: 이 속성은 사용자 이름(또는 다른 식별자)을 입력할 수 있는 필드를 정의합니다. 사용자는 로그인할 때 이 필드에 사용자 이름을 입력해야 합니다. label 속성을 사용하여 필드 레이블을 정의할 수 있습니다.
password: 이 속성은 비밀번호를 입력할 수 있는 필드를 정의합니다. 사용자는 로그인할 때 이 필드에 비밀번호를 입력해야 합니다. label 속성을 사용하여 필드 레이블을 정의할 수 있습니다. type 속성은 입력 필드의 타입을 지정하는 데 사용되며, 여기서는 password로 설정되어 비밀번호 입력 필드임을 나타냅니다.
Providers.Credentials를 사용하여 사용자 정의 자격 증명 로그인을 구현하면 다음과 같은 작업이 가능합니다:
-
credentials 객체를 정의하여 사용자 이름과 비밀번호와 같은 필요한 자격 증명 필드를 설정합니다.
-
로그인 화면에서 signIn 함수를 호출하여 자체 로그인을 실행하고, signIn 함수의 첫 번째 인자로는 credentials 로그인 공급자(provider)의 이름을 전달하고, 두 번째 인자로는 사용자가 입력한 자격 증명 정보(예: 사용자 이름과 비밀번호)를 전달합니다.
-
이 때, signIn 함수를 호출하면 authorize 콜백 함수가 실행됩니다. authorize 함수에서는 외부 서버와 통신하여 사용자 정보와 토큰을 가져오는 로직을 구현합니다.
-
외부 서버와의 통신 결과를 통해 로그인이 성공하면 해당 사용자 정보와 토큰을 NextAuth.js 세션에 저장하고, 그렇지 않으면 로그인 실패를 처리할 수 있습니다.
import { signIn } from 'next-auth/react'; function LoginPage() { const handleSubmit = async (e) => { e.preventDefault(); // 사용자가 입력한 사용자 이름(username)과 비밀번호(password)를 가져옵니다. const username = e.target.username.value; const password = e.target.password.value; // signIn 함수를 사용하여 자체 로그인 요청을 보냅니다. const result = await signIn('credentials', { username, password, // 필요한 경우 다른 필드도 추가할 수 있습니다. }); // 로그인이 성공하면 다음 페이지로 이동할 수 있습니다. if (result.error) { // 로그인 실패 시 오류 메시지를 처리할 수 있습니다. console.error(result.error); } }; return ( <div> <h1>로그인</h1> <form onSubmit={handleSubmit}> <div> <label htmlFor="username">사용자 이름</label> <input type="text" id="username" name="username" /> </div> <div> <label htmlFor="password">비밀번호</label> <input type="password" id="password" name="password" /> </div> <button type="submit">로그인</button> </form> </div> ); } export default LoginPage;
session
NextAuth.js는 클라이언트와 서버 간의 통신을 담당하는 라이브러리입니다. 이 라이브러리는 서버 측에서 실행되며, 서버에서 사용자 인증 및 세션 관리와 관련된 작업을 수행합니다. 클라이언트는 NextAuth.js를 사용하여 서버에서 제공하는 API 엔드포인트를 호출하고 세션을 관리할 수 있습니다.
NextAuth.js를 사용하면 클라이언트는 서버 측 세션에 직접 접근하거나 조작할 수 없으며, 사용자 인증 및 세션 관리가 서버 측에서 안전하게 처리됩니다. 이것이 NextAuth.js를 사용하는 주요 이점 중 하나입니다. 사용자 정보 및 세션 관리는 서버에서 안전하게 보호되며, 클라이언트는 이러한 데이터에 대한 직접적인 액세스를 허용하지 않습니다.
따라서 NextAuth.js를 사용하면 클라이언트와 서버 간의 보안 통신을 효과적으로 처리할 수 있으며, 사용자 정보와 세션 데이터를 안전하게 관리할 수 있습니다. 다만, NextAuth.js를 사용하는 서버 자체의 보안 설정과 모범 사례를 따르는 것이 중요하며, 서버 측 보안에도 신경을 써야 합니다.
NextAuth.js에서 사용자 정보와 토큰을 세션에 저장하는 방법은 다음과 같이 두 가지입니다.
1. authorize 콜백 함수 내에서 반환한 객체에 포함된 토큰 정보:
const response = await axios.post('https://your-external-server.com/api/login', { username, password }); const data = response.data; if (data) { // 유저 정보와 토큰을 NextAuth.js 세션에 저장합니다. return { name: data.name, email: data.email, token: data.token // 이 토큰 정보는 세션에 자동으로 저장됩니다. }; }
위의 코드에서 authorize 함수 내에서 반환한 객체에 token 정보가 포함되어 있으며, 이 정보는 NextAuth.js에서 자동으로 세션에 저장됩니다. 이 방법은 대부분의 경우에 사용하기 적합하며 간단하고 편리합니다.
2. callbacks 설정 내의 session 콜백 함수:
callbacks: { async session(session, token) { // 세션에 토큰 정보를 추가합니다. session.token = token.token; // 이 부분에서 세션에 토큰 정보를 수동으로 추가합니다. return session; } },
위의 코드에서는 session 콜백 함수를 사용하여 세션 객체에 직접 토큰 정보를 추가하는 방법을 사용합니다. 이 방법은 특별한 사용 사례나 커스터마이징이 필요한 경우에 유용합니다. 예를 들어 세션 객체에 토큰 이외의 사용자 지정 데이터를 추가하거나, 세션에 특정 조건에 따라 다른 정보를 추가해야 할 때 사용합니다.
따라서 프로젝트의 요구 사항과 사용자 인증 및 세션 관리의 세부 사항에 따라 두 가지 방법 중 하나를 선택할 수 있습니다. 일반적으로 자동 방식이 간단하고 효율적이며 대부분의 경우에 적합합니다. 다만 특별한 커스텀 요구 사항이 있는 경우 수동 방식을 사용하여 세션을 더욱 커스터마이즈할 수 있습니다.
보안과 JWT
session: { jwt: true } 설정은 필수적으로 사용해야 하는 것은 아닙니다. NextAuth.js를 사용하는 프로젝트에 따라 JWT를 사용할 필요가 없는 경우도 있을 수 있습니다. JWT를 사용하느냐 마느냐는 프로젝트의 요구 사항과 보안 관련 결정에 따라 다를 수 있습니다.
JWT를 사용하지 않을 경우에는 기본적인 세션 관리 방식을 사용하게 됩니다. 이 경우, 세션 데이터가 서버 측에서 안전하게 저장되며 NextAuth.js가 세션을 처리합니다. 이 방법도 보안적으로 충분하며, JWT를 사용할 필요가 없는 경우에는 session: { jwt: true } 설정을 제외하고 설정할 수 있습니다.
따라서 JWT를 사용할지 여부는 프로젝트의 요구 사항과 보안 정책에 따라 결정해야 합니다. 일부 프로젝트에서는 JWT를 사용하여 추가적인 보안과 효율성을 얻을 수 있지만, 다른 프로젝트에서는 기본 세션 관리 방식을 사용하여 간단하게 구현할 수도 있습니다.
하지만 일반적으로 session: { jwt: true } 설정을 사용하는 것이 좋습니다. 이 설정은 다음과 같은 이점을 제공합니다:
보안: JWT는 서버에서 서명되어 클라이언트에서 수정할 수 없습니다. 이로써 세션 데이터의 무결성이 보장됩니다.
효율성: JWT는 클라이언트 측에서 저장되므로 서버에 저장된 세션과 비교하여 서버 부하를 줄일 수 있습니다.
쉬운 전달: JWT는 HTTP 헤더 또는 쿠키와 같은 방법으로 클라이언트와 서버 간에 쉽게 전달할 수 있습니다.
저장된 유저정보 사용하기
먼저 세션을 사용하려면 애플리케이션의 최상위 수준에서 세션 컨텍스트인 <SessionProvider />를 사용해야 합니다.
import { SessionProvider } from "next-auth/react" export default function App({ Component, pageProps: { session, ...pageProps }, }) { return ( <SessionProvider session={session}> <Component {...pageProps} /> </SessionProvider> ) }
로그인이 필요한 페이지
import { useSession, signIn, signOut } from 'next-auth/react'; function ProtectedPage() { const { data: session } = useSession(); if (!session) { return ( <div> <p>이 페이지는 로그인이 필요한 페이지입니다.</p> <button onClick={()=> signIn('credentials')}>로그인</button> </div> ); } return ( <div> <p>안녕하세요, {session.user.name}님!</p> <button onClick={()=> signOut()}>로그아웃</button> </div> ); } export default ProtectedPage;
로그아웃
signOut() 함수를 호출하는 것만으로 세션을 종료하고 로그아웃을 수행합니다. NextAuth.js는 이를 처리하고 현재 세션을 삭제하여 로그아웃 상태로 전환합니다. 따라서 signOut() 함수를 호출할 때 별도의 설정이나 세션 삭제 코드를 작성할 필요가 없습니다.
간단하게 signOut() 함수를 호출하여 로그아웃을 실행할 수 있으며, 사용자가 다시 로그인해야 합니다. 이것은 세션을 관리하는 NextAuth.js의 기능 중 하나로, 로그아웃을 처리하려면 별도의 로직을 작성할 필요가 없습니다.
소셜 로그인
import NextAuth from 'next-auth'; import Providers from 'next-auth/providers'; export default NextAuth({ providers: [ Providers.GitHub({ clientId: process.env.GITHUB_CLIENT_ID, clientSecret: process.env.GITHUB_CLIENT_SECRET, }), Providers.Naver({ clientId: process.env.NAVER_CLIENT_ID, clientSecret: process.env.NAVER_CLIENT_SECRET, }), // 다른 소셜 로그인 공급자 설정 가능 ], // ... });
import { signIn } from 'next-auth/react'; function LoginPage() { return ( <div> <button onClick={()=> signIn('github')}>GitHub 로그인</button> </div> ); } export default LoginPage;
소셜 로그인 후 자체 로그인처럼 토큰이나 유저정보를 별도로 세션에 저장하는 로직을 구현할 필요가 없습니다. NextAuth.js 가 알아서 세션에 저장해주며
const { data: session } = useSession(); 를 이용해 사용하면 됩니다.