Post

Node.js JWT 인증 시스템 완성하기 Part 4: 대시보드 구현 및 완전한 인증 플로우

Node.js JWT 인증 시스템 완성하기 Part 4: 대시보드 구현 및 완전한 인증 플로우

🔗 시리즈 연결

이 포스트는 JWT 인증 시스템 시리즈의 Part 4입니다.

🤔 Part 3에서 남은 문제

Part 3에서 로그인/회원가입 폼을 완성했지만… 한 가지 문제가 있었어요! 😅

“로그인은 성공하는데, 로그인 후에 뭘 보여줘야 하지?” 🤷‍♂️

지금까지는 로그인 성공해도 계속 로그인 폼만 보여주고 있었거든요. 실제 웹사이트처럼 로그인 후 사용자 대시보드가 필요해요!

🎯 Part 4에서 구현할 기능들

  • ✅ 로그인 상태 기반 화면 전환 🔄
  • ✅ 사용자 대시보드 컴포넌트 📊
  • ✅ 사용자 정보 표시 (이름, 이메일, 가입일) 👤
  • ✅ 로그아웃 기능 구현 🚪
  • ✅ 모던하고 깔끔한 대시보드 디자인 ✨

🏗️ Step 1: App.js 라우팅 로직 개선

먼저 로그인 상태에 따라 화면을 전환하는 로직을 만들어보겠습니다!

🔍 현재 App.js의 문제점

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 기존 App.js - 문제가 있는 코드
function App() {
	const [currentView, setCurrentView] = useState("login");
	
	// 🚨 문제: 로그인 상태를 확인하지 않음!
	return (
		<div className="App">
			{currentView === "login" ? (
				<LoginForm onSwitchToRegister={() => setCurrentView("register")} />
			) : (
				<RegisterForm onSwitchToLogin={() => setCurrentView("login")} />
			)}
		</div>
	);
}

✨ 개선된 App.js

src/App.js

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
import React, { useState, useContext } from "react";
import { AuthContext } from "./components/context/AuthContext";
import LoginForm from "./components/Auth/LoginForm";
import RegisterForm from "./components/Auth/RegisterForm";
import Dashboard from "./components/Dashboard/Dashboard";
import "./App.css";

function App() {
	const [currentView, setCurrentView] = useState("login");
	const { user } = useContext(AuthContext); // 🔑 핵심! 로그인 상태 확인

	// 로그인되어 있으면 대시보드 보여주기
	if (user) {
		return <Dashboard />;
	}

	// 로그인 안되어 있으면 로그인/회원가입 폼 보여주기
	return (
		<div className="App">
			{currentView === "login" ? (
				<LoginForm
					onSwitchToRegister={() => setCurrentView("register")}
				/>
			) : (
				<RegisterForm onSwitchToLogin={() => setCurrentView("login")} />
			)}
		</div>
	);
}

export default App;

💡 핵심 변경점

  1. AuthContext import: useContext(AuthContext)로 로그인 상태 확인
  2. 조건부 렌더링: if (user) 조건으로 대시보드/로그인폼 전환
  3. 깔끔한 로직: 로그인되면 자동으로 대시보드 표시

📊 Step 2: Dashboard 컴포넌트 구현

이제 로그인 후 보여줄 멋진 대시보드를 만들어보겠습니다!

📁 폴더 구조

1
2
3
src/components/Dashboard/
├── Dashboard.jsx
└── Dashboard.css

🎯 Dashboard 컴포넌트

src/components/Dashboard/Dashboard.jsx

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
import React, { useContext } from "react";
import { AuthContext } from "../context/AuthContext";
import "./Dashboard.css";

const Dashboard = () => {
	const { user, logout } = useContext(AuthContext);

	const handleLogout = async () => {
		try {
			await logout();
		} catch (error) {
			console.error("로그아웃 에러:", error);
		}
	};

	return (
		<div className="dashboard-container">
			<div className="dashboard-card">
				<div className="dashboard-header">
					<h1 className="dashboard-title">환영합니다!</h1>
					<p className="dashboard-subtitle">
						로그인이 성공적으로 완료되었습니다
					</p>
				</div>

				<div className="user-info">
					<div className="user-avatar">
						<span className="avatar-icon">👤</span>
					</div>
					<div className="user-details">
						<h2 className="user-name">{user?.username}</h2>
						<p className="user-email">{user?.email}</p>
						<p className="user-joined">
							가입일:{" "}
							{new Date(user?.createdAt).toLocaleDateString("ko-KR")}
						</p>
					</div>
				</div>

				<div className="dashboard-actions">
					<button onClick={handleLogout} className="logout-button">
						로그아웃
					</button>
				</div>
			</div>
		</div>
	);
};

export default Dashboard;

🔧 주요 기능 설명

  1. 사용자 정보 표시: user?.username, user?.email 등으로 안전하게 접근
  2. 가입일 포맷팅: toLocaleDateString('ko-KR')로 한국어 날짜 형식
  3. 로그아웃 처리: handleLogout 함수로 안전한 로그아웃
  4. 에러 처리: try-catch로 로그아웃 에러 핸들링

🎨 Step 3: 모던한 대시보드 스타일링

사용자가 깔끔하고 모던하다고 느낄만한 디자인을 만들어보겠습니다!

src/components/Dashboard/Dashboard.css

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
.dashboard-container {
	display: flex;
	justify-content: center;
	align-items: center;
	min-height: 100vh;
	background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
	padding: 1rem;
}

.dashboard-card {
	background: white;
	padding: 3rem;
	border-radius: 16px;
	box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
	width: 100%;
	max-width: 500px;
	text-align: center;
}

.dashboard-header {
	margin-bottom: 2.5rem;
}

.dashboard-title {
	color: #333;
	font-size: 2.5rem;
	font-weight: 700;
	margin-bottom: 0.5rem;
}

.dashboard-subtitle {
	color: #666;
	font-size: 1.1rem;
}

.user-info {
	background: #f8f9fa;
	padding: 2rem;
	border-radius: 12px;
	margin-bottom: 2rem;
	display: flex;
	align-items: center;
	gap: 1.5rem;
}

.user-avatar {
	flex-shrink: 0;
}

.avatar-icon {
	display: inline-block;
	width: 80px;
	height: 80px;
	background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
	border-radius: 50%;
	font-size: 2.5rem;
	line-height: 80px;
	color: white;
}

.user-details {
	flex: 1;
	text-align: left;
}

.user-name {
	color: #333;
	font-size: 1.8rem;
	font-weight: 600;
	margin-bottom: 0.5rem;
}

.user-email {
	color: #666;
	font-size: 1.1rem;
	margin-bottom: 0.5rem;
}

.user-joined {
	color: #888;
	font-size: 0.95rem;
}

.dashboard-actions {
	margin-top: 1.5rem;
}

.logout-button {
	background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
	color: white;
	border: none;
	padding: 0.9rem 2rem;
	border-radius: 8px;
	font-size: 1.1rem;
	font-weight: 600;
	cursor: pointer;
	transition: all 0.2s ease;
}

.logout-button:hover {
	transform: translateY(-2px);
	box-shadow: 0 8px 20px rgba(231, 76, 60, 0.3);
}

/* 반응형 디자인 */
@media (max-width: 768px) {
	.user-info {
		flex-direction: column;
		text-align: center;
	}
	
	.user-details {
		text-align: center;
	}
	
	.dashboard-card {
		padding: 2rem;
	}
}

🎨 디자인 특징

  • 그라데이션 배경: 로그인 폼과 일관된 디자인
  • 카드 레이아웃: 깔끔한 카드 스타일
  • 사용자 아바타: 원형 아이콘으로 개성 표현
  • 호버 효과: 버튼에 부드러운 애니메이션
  • 반응형: 모바일에서도 완벽하게 동작

🔧 Step 4: 로그아웃 기능 개선

로그아웃에서 발생할 수 있는 에러를 안전하게 처리해보겠습니다!

🚨 발생할 수 있는 문제

  • 백엔드 서버가 다운된 경우
  • 토큰이 이미 만료된 경우
  • 네트워크 연결 문제

✨ 개선된 로그아웃 함수

src/components/context/AuthContext.jsx 수정:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 로그아웃 함수 - 에러 처리 개선
const logout = async () => {
	try {
		// 토큰이 있을 때만 서버에 로그아웃 알리기
		const token = localStorage.getItem("accessToken");
		if (token) {
			await api.post("/auth/logout");
		}
	} catch (error) {
		// 서버 에러가 있어도 로컬에서는 로그아웃 진행
		console.warn("서버 로그아웃 실패, 로컬 로그아웃 진행:", error);
	} finally {
		// 어떤 경우든 로컬 상태는 정리
		localStorage.removeItem("accessToken");
		setUser(null);
	}
};

💡 개선된 점

  1. 토큰 확인: 토큰이 있을 때만 서버 호출
  2. 에러 무시: 서버 에러가 있어도 로컬 로그아웃 진행
  3. 안전한 정리: finally 블록으로 확실한 상태 정리
  4. 사용자 경험: 어떤 상황에서도 로그아웃 성공

🚀 Step 5: 실행 및 테스트

이제 완성된 인증 플로우를 테스트해보겠습니다!

🔧 서버 실행

1
2
3
4
5
6
7
# 백엔드 서버 실행
cd backend
npm run dev

# 프론트엔드 서버 실행
cd frontend  
npm start

🧪 완전한 테스트 시나리오

  1. 초기 화면 📱
    • http://localhost:3000 접속
    • 로그인 폼이 표시되는지 확인
  2. 회원가입 플로우 📝
    • “회원가입” 클릭 → 회원가입 폼으로 전환
    • 새 계정 생성
    • 성공 메시지 후 로그인 폼으로 자동 이동
  3. 로그인 플로우 🔑
    • 가입한 계정으로 로그인
    • 자동으로 대시보드 화면으로 전환!
  4. 대시보드 확인 📊
    • 사용자 이름, 이메일 정보 표시
    • 가입일이 한국어 형식으로 표시
    • 예쁜 아바타 아이콘 확인
  5. 로그아웃 테스트 🚪
    • “로그아웃” 버튼 클릭
    • 다시 로그인 폼으로 돌아가는지 확인
  6. 페이지 새로고침 테스트 🔄
    • 로그인 상태에서 F5 새로고침
    • 여전히 대시보드가 표시되는지 확인 (토큰 자동 복원!)

💡 핵심 포인트 정리

1. 상태 기반 라우팅 🔄

  • AuthContext 활용: user 상태로 로그인 여부 판단
  • 조건부 렌더링: 간단한 if 문으로 화면 전환
  • 자동 전환: 로그인 성공 시 즉시 대시보드 표시

2. 사용자 경험 개선 ✨

  • 즉시 피드백: 로그인 성공 시 바로 대시보드 표시
  • 정보 표시: 사용자 이름, 이메일, 가입일 등 개인화된 정보
  • 직관적 UI: 아바타, 카드 레이아웃으로 친근한 느낌

3. 에러 처리 강화 🛡️

  • Graceful Logout: 서버 에러가 있어도 로컬 로그아웃 진행
  • 안전한 접근: user?.username 등으로 null 체크
  • 사용자 친화적: 에러가 있어도 기능은 정상 동작

4. 모던한 디자인 🎨

  • 일관된 테마: 로그인 폼과 동일한 그라데이션 배경
  • 반응형 레이아웃: 모바일에서도 완벽하게 동작
  • 부드러운 애니메이션: 호버 효과로 인터랙티브한 느낌

🎉 Part 4 완성!

축하합니다! 🎊 이제 우리의 JWT 인증 시스템이 완전한 사용자 플로우를 갖게 되었어요!

✅ 지금까지 완성된 기능들

  • 🔐 안전한 회원가입/로그인 (Part 1, 2)
  • 🎨 아름다운 React UI (Part 3)
  • 📊 사용자 대시보드 (Part 4)
  • 🔄 완전한 인증 플로우 (Part 4)
  • 🛡️ 강력한 에러 처리 (Part 4)

🔮 다음 Part 5에서는

  • 🛣️ React Router - 여러 페이지 간 이동
  • 🔄 자동 토큰 갱신 - 끊김 없는 사용자 경험
  • Protected Routes - 인증이 필요한 페이지 보호
  • 🎯 사용자 프로필 관리 - 정보 수정 기능

🔗 GitHub 저장소

현재까지의 완성된 코드는 GitHub 리포지토리에서 확인할 수 있습니다! 🚀

1
2
3
4
# Part 4 브랜치 클론
git clone https://github.com/hoondongseo/SimpleAuthSystem.git
cd SimpleAuthSystem
git checkout feature/frontend

🎯 실무에서 이런 식으로 사용해요!

오늘 구현한 패턴은 실제 회사에서도 많이 사용하는 방식이에요:

  • 네이버, 카카오: 로그인 후 메인 페이지로 리다이렉트
  • GitHub, Notion: 사용자 대시보드에서 개인화된 정보 표시
  • Instagram, Facebook: 프로필 아바타와 사용자 정보 UI

여러분도 이제 실무급 인증 시스템을 만들 수 있게 되었어요! 💪


💡 질문이나 피드백이 있으시면 댓글로 남겨주세요! 😊
Part 5에서는 더욱 고급 기능들을 다뤄보겠습니다! 🚀

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