로그인 및 로그아웃 구현
이전 절에서 NextAuth.js를 Next.js App Router 프로젝트에 설정하는 기본적인 방법을 알아보았습니다. 이제 이 설정을 바탕으로 실제 웹 애플리케이션에서 사용자가 로그인하고 로그아웃하는 기능을 어떻게 구현하는지 상세히 살펴보겠습니다. NextAuth.js는 클라이언트 컴포넌트와 서버 컴포넌트 모두에서 인증 상태를 안전하게 관리하고 접근할 수 있는 유연한 API를 제공합니다.
이 절에서는 다음 내용을 다룹니다.
signIn()
함수를 사용한 로그인 구현signOut()
함수를 사용한 로그아웃 구현- 로그인 상태에 따른 UI 조건부 렌더링
- 보호된 라우트(Protected Routes) 구현의 기본 개념
signIn()
함수를 사용한 로그인 구현
NextAuth.js는 next-auth/react
에서 제공하는 signIn()
함수를 통해 다양한 인증 Provider를 이용한 로그인을 쉽게 시작할 수 있도록 합니다.
signIn()
함수의 기본 사용법
signIn()
함수는 두 가지 주요 인자를 받습니다.
-
Provider ID (선택 사항): 로그인할 Provider의 ID입니다. (예:
'github'
,'google'
,'credentials'
) 이 인자를 생략하면 NextAuth.js가 제공하는 기본 로그인 페이지로 리다이렉트되어 사용자가 Provider를 선택할 수 있도록 합니다. -
Options 객체 (선택 사항): 로그인 후 리다이렉트할 URL, 에러 발생 시 처리 방법 등을 설정할 수 있습니다.
callbackUrl
: 로그인 성공 후 리다이렉트될 URL입니다. 기본값은 현재 페이지입니다.redirect
: 로그인 성공 후 자동으로 리다이렉트할지 여부를 결정합니다.false
로 설정하면 리다이렉트하지 않고 세션 정보만 업데이트됩니다. (일반적으로true
가 기본값이며, 명시적으로 설정할 필요는 없습니다.)
예시
import { signIn } from 'next-auth/react';
// GitHub 로그인 버튼 클릭 시
<button onClick={() => signIn('github')}>GitHub로 로그인</button>
// 로그인 성공 후 특정 페이지로 리다이렉트
<button onClick={() => signIn('google', { callbackUrl: '/dashboard' })}>Google로 로그인</button>
// 자격 증명(Credentials) 로그인 (사용자 이름/비밀번호)
// credentialsProvider는 별도의 설정이 필요하며, 일반적으로 폼 데이터를 전달합니다.
// signIn('credentials', { username: 'testuser', password: 'password123', callbackUrl: '/dashboard' });
로그인 버튼 컴포넌트 구현
이전 절에서 사용했던 AuthButtons.tsx
컴포넌트를 다시 살펴보겠습니다.
// src/app/auth/AuthButtons.tsx
"use client";
import { useSession, signIn, signOut } from 'next-auth/react';
export default function AuthButtons() {
const { data: session } = useSession();
if (session) {
// 로그인 상태일 때는 로그아웃 버튼 표시
return (
<button
onClick={() => signOut({ callbackUrl: '/' })}
style={{
padding: '12px 25px',
backgroundColor: '#dc3545',
color: 'white',
border: 'none',
borderRadius: '8px',
cursor: 'pointer',
fontSize: '1.1em',
transition: 'background-color 0.3s ease'
}}
>
로그아웃
</button>
);
}
// 로그아웃 상태일 때는 로그인 버튼 표시
return (
<button
onClick={() => signIn('github', { callbackUrl: '/auth' })} // GitHub 로그인, 로그인 후 /auth 페이지로 리다이렉트
style={{
padding: '12px 25px',
backgroundColor: '#007bff',
color: 'white',
border: 'none',
borderRadius: '8px',
cursor: 'pointer',
fontSize: '1.1em',
transition: 'background-color 0.3s ease'
}}
>
GitHub로 로그인
</button>
);
}
이 컴포넌트는 useSession()
훅을 사용하여 현재 사용자의 로그인 상태를 확인하고, session
객체의 존재 여부에 따라 "로그아웃" 버튼 또는 "GitHub로 로그인" 버튼을 조건부로 렌더링합니다.
signOut()
함수를 사용한 로그아웃 구현
로그인과 마찬가지로, signOut()
함수는 현재 사용자 세션을 종료하는 데 사용됩니다.
signOut()
함수의 기본 사용법
signOut()
함수는 signIn()
과 유사하게 Options 객체를 인자로 받습니다.
callbackUrl
: 로그아웃 성공 후 리다이렉트될 URL입니다. 기본값은 현재 페이지입니다.redirect
: 로그아웃 성공 후 자동으로 리다이렉트할지 여부를 결정합니다.false
로 설정하면 리다이렉트하지 않고 세션 정보만 제거됩니다.
예시
import { signOut } from 'next-auth/react';
// 로그아웃 버튼 클릭 시
<button onClick={() => signOut()}>로그아웃</button>
// 로그아웃 후 홈 페이지로 리다이렉트
<button onClick={() => signOut({ callbackUrl: '/' })}>로그아웃</button>
로그아웃 버튼 컴포넌트 구현
위 AuthButtons.tsx
에서 session
이 존재할 때 렌더링되는 "로그아웃" 버튼이 바로 signOut()
함수를 사용하는 예시입니다. callbackUrl: '/'
을 설정하여 로그아웃 후 애플리케이션의 루트 페이지로 이동하도록 했습니다.
로그인 상태에 따른 UI 조건부 렌더링
useSession()
훅은 session
객체와 status
(세션 로딩 상태)를 반환합니다. 이 정보를 활용하여 사용자의 로그인 상태에 따라 다른 UI를 보여줄 수 있습니다.
status === 'loading'
: 세션 정보를 가져오는 중. 로딩 스피너 등을 보여줄 수 있습니다.status === 'authenticated'
: 사용자가 로그인된 상태. 사용자 이름, 프로필 이미지 등을 표시할 수 있습니다.status === 'unauthenticated'
: 사용자가 로그인되지 않은 상태. 로그인 버튼이나 로그인 유도 메시지를 보여줄 수 있습니다.
이전 절의 UserInfo.tsx
컴포넌트가 이 원리를 잘 보여줍니다.
// src/app/auth/UserInfo.tsx
"use client";
import { useSession } from 'next-auth/react';
export default function UserInfo() {
const { data: session, status } = useSession(); // status도 함께 가져옴
if (status === 'loading') {
return <p>세션 정보를 로딩 중입니다...</p>; // 로딩 상태 처리
}
if (session) {
// 로그인된 사용자 정보 표시
return (
<div style={{ padding: '15px', backgroundColor: '#e6ffe6', borderRadius: '8px', border: '1px solid #b3ffb3' }}>
<p style={{ fontWeight: 'bold', color: '#28a745', marginBottom: '10px' }}>로그인됨!</p>
<p>이름: {session.user?.name}</p>
<p>이메일: {session.user?.email}</p>
{session.user?.image && (
<img src={session.user.image} alt="User Avatar" style={{ width: '50px', height: '50px', borderRadius: '50%', marginTop: '10px' }} />
)}
<p style={{ marginTop: '10px', fontSize: '0.9em', color: '#555' }}>
(이 정보는 클라이언트 컴포넌트에서 `useSession` 훅으로 가져왔습니다.)
</p>
</div>
);
}
// 로그인되지 않은 상태 메시지 표시
return (
<p style={{ padding: '15px', backgroundColor: '#ffe6e6', borderRadius: '8px', border: '1px solid #ffb3b3', color: '#dc3545' }}>
로그아웃되었습니다.
<p style={{ fontSize: '0.9em', color: '#555', marginTop: '5px' }}>
(클라이언트 컴포넌트에서 세션이 없습니다.)
</p>
</p>
);
}
보호된 라우트 구현의 기본 개념
특정 페이지나 리소스에 대한 접근을 로그인된 사용자에게만 허용해야 하는 경우가 많습니다. 이를 보호된 라우트 (Protected Routes) 라고 합니다. Next.js App Router에서 보호된 라우트를 구현하는 방법은 크게 두 가지입니다.
클라이언트 컴포넌트에서 구현
클라이언트 컴포넌트 내에서 useSession()
훅을 사용하여 사용자의 인증 상태를 확인하고, 로그인되지 않았다면 로그인 페이지로 리다이렉트하거나 접근 거부 메시지를 표시할 수 있습니다.
// src/app/dashboard/page.tsx (클라이언트 컴포넌트 예시)
"use client";
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
export default function DashboardPage() {
const { data: session, status } = useSession();
const router = useRouter();
useEffect(() => {
if (status === 'loading') return; // 세션 로딩 중에는 아무것도 하지 않음
if (!session) {
// 로그인되지 않았다면 로그인 페이지로 리다이렉트
router.push('/api/auth/signin'); // NextAuth.js 기본 로그인 페이지로
// 또는 router.push('/login'); // 커스텀 로그인 페이지로
}
}, [session, status, router]);
if (status === 'loading') {
return <p>대시보드를 로딩 중입니다...</p>;
}
if (!session) {
return <p>이 페이지에 접근하려면 로그인해야 합니다.</p>;
}
// 로그인된 경우 대시보드 콘텐츠 렌더링
return (
<div style={{ padding: '20px', maxWidth: '800px', margin: '20px auto', border: '1px solid #28a745', borderRadius: '10px', boxShadow: '0 4px 8px rgba(0,0,0,0.1)', textAlign: 'center' }}>
<h1 style={{ color: '#28a745', marginBottom: '20px' }}>대시보드</h1>
<p>환영합니다, {session.user?.name}님! 이곳은 보호된 대시보드 페이지입니다.</p>
<p style={{ marginTop: '10px', fontSize: '0.9em', color: '#555' }}>
(이 페이지는 로그인된 사용자만 접근할 수 있습니다.)
</p>
</div>
);
}
주의: 클라이언트 측 리다이렉트는 사용자가 잠시 동안 보호된 콘텐츠를 볼 수 있는 "깜빡임(flash)" 현상이 발생할 수 있습니다. 더 안전하고 SEO 친화적인 방법은 서버 측에서 인증을 확인하는 것입니다.
서버 컴포넌트에서 구현 (추천)
Next.js App Router의 강력한 기능 중 하나는 서버 컴포넌트에서 직접 인증 상태를 확인할 수 있다는 것입니다. 이는 클라이언트 측 깜빡임 없이 서버에서 즉시 리다이렉트하거나 접근을 거부할 수 있게 해줍니다.
// src/app/admin/page.tsx (서버 컴포넌트 예시)
import { getServerSession } from 'next-auth';
import { redirect } from 'next/navigation'; // Next.js 서버 컴포넌트에서 리다이렉트
import { authOptions } from '../api/auth/[...nextauth]/route'; // 인증 옵션 임포트
export default async function AdminPage() {
const session = await getServerSession(authOptions);
if (!session) {
// 로그인되지 않았다면 로그인 페이지로 즉시 리다이렉트
redirect('/api/auth/signin?callbackUrl=/admin'); // 로그인 후 다시 admin 페이지로 오도록 callbackUrl 설정
}
// 선택적으로 사용자 역할 확인 (예: 'admin' 역할만 허용)
// if (session.user?.role !== 'admin') {
// redirect('/unauthorized'); // 권한 없음 페이지로 리다이렉트
// }
return (
<div style={{ padding: '20px', maxWidth: '800px', margin: '20px auto', border: '1px solid #ffc107', borderRadius: '10px', boxShadow: '0 4px 8px rgba(0,0,0,0.1)', textAlign: 'center' }}>
<h1 style={{ color: '#ffc107', marginBottom: '20px' }}>관리자 페이지</h1>
<p>환영합니다, {session.user?.name}님! 이곳은 관리자만 접근할 수 있는 페이지입니다.</p>
<p style={{ marginTop: '10px', fontSize: '0.9em', color: '#555' }}>
(이 페이지는 서버 컴포넌트에서 인증 상태를 확인하여 보호됩니다.)
</p>
</div>
);
}
이 방식은 서버에서 렌더링되기 전에 인증 상태를 확인하므로, 보안적으로 더 강력하고 사용자 경험 측면에서도 더 부드럽습니다.
실습: 로그인/로그아웃 및 보호된 페이지 확인
이전 절에서 설정한 NextAuth.js 환경을 바탕으로, 위에서 설명한 AuthButtons.tsx
, UserInfo.tsx
그리고 새로운 dashboard/page.tsx
, admin/page.tsx
를 생성/수정하여 테스트해 보세요.
src/app/dashboard/page.tsx
생성 (클라이언트 컴포넌트 예시)src/app/admin/page.tsx
생성 (서버 컴포넌트 예시)- 개발 서버(
npm run dev
)를 실행하고 다음 경로로 접속하며 테스트합니다:http://localhost:3000/auth
: 로그인/로그아웃 버튼과 사용자 정보가 표시됩니다.http://localhost:3000/dashboard
: 로그인하지 않은 상태에서 접속하면 로그인 페이지로 리다이렉트되거나 접근 거부 메시지가 표시됩니다. 로그인 후 다시 접속하면 대시보드 콘텐츠가 보입니다.http://localhost:3000/admin
: 로그인하지 않은 상태에서 접속하면 로그인 페이지로 즉시 리다이렉트됩니다. 로그인 후 다시 접속하면 관리자 페이지 콘텐츠가 보입니다.
로그인 및 로그아웃 기능 구현은 NextAuth.js의 핵심이며, 이를 통해 사용자에게 안전하고 원활한 인증 경험을 제공할 수 있습니다. 다음 절에서는 더 나아가 데이터베이스를 연동하여 사용자 정보를 영구적으로 저장하고 관리하는 방법을 알아보겠습니다.