회원관리 - Part2 로그인 with JWT 토큰
ㆍProject Diary/Next.js + Prisma + MariaDB (KiloFlow)
로그인 기능은 사용자가 이메일과 비밀번호를 입력하면 서버에서 이를 확인하고, JWT(JSON Web Token)를 발급하여 인증을 수행합니다. JWT 토큰을 사용하여 현재 로그인된 유저 정보를 가져오는 방법도 함께 다룹니다.
필요 개념 설명
JWT 토큰이란?
JWT(JSON Web Token)는 JSON 객체를 사용하여 양쪽 간에 정보를 안전하게 전송하기 위한 컴팩트하고 자가 포함된 방식입니다. JWT는 주로 인증 및 정보 교환에 사용됩니다. 다음과 같은 세 가지 주요 부분으로 구성됩니다:
- Header : 토큰의 유형과 해싱 알고리즘을 정의합니다.
- Payload : 토큰의 주된 데이터가 포함됩니다.
- Signature : Header와 Payload의 무결성을 확인하기 위해 사용됩니다.
프론트엔드 : 로그인 페이지
로그인 페이지에서는 사용자가 이메일과 비밀번호를 입력하고 서버에 인증 요청을 보냅니다. 성공적으로 인증되면 JWT 토큰을 로컬 저장소에 저장합니다.
로그인 함수
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
setError("");
// 이메일과 비밀번호가 입력되었는지 확인
if (!email || !password) {
setError("이메일과 비밀번호를 입력해주세요.");
return;
}
try {
// 서버에 로그인 요청
const res = await fetch("/api/auth/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email, password }),
});
// 로그인 성공 시
if (res.ok) {
const data = await res.json();
localStorage.setItem("token", data.token);
if (data.isInitialSetupComplete) {
router.push("/"); // 초기 설정 완료 시 메인 페이지로 이동
} else {
router.push("/initialSetting"); // 초기 설정 미완료 시 설정 페이지로 이동
}
} else {
// 로그인 실패 시
const data = await res.json();
setError(data.message);
}
} catch (err) {
setError("An unexpected error occurred");
}
};
(설명 : 주석 참고)
백엔드 : 로그인 API 엔드포인트
1. 로그인 요청 처리
import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "../../../lib/prisma";
import jwt from "jsonwebtoken";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// POST 메소드인지 확인
if (req.method !== "POST") {
return res.status(405).json({ error: `Method ${req.method} Not Allowed` });
}
// 요청 본문에서 이메일과 비밀번호 추출
const { email, password } = req.body;
// 이메일과 비밀번호가 제공되었는지 확인
if (!email || !password) {
return res.status(400).json({ message: "이메일과 비밀번호를 입력해주세요." });
}
try {
// 데이터베이스에서 해당 이메일을 가진 사용자 찾기
const user = await prisma.users.findUnique({ where: { email } });
// 사용자가 존재하지 않거나 비밀번호가 일치하지 않는 경우
if (!user || user.password !== password) {
return res.status(401).json({ message: "잘못된 이메일 또는 비밀번호입니다." });
}
// JWT 토큰 생성
const token = jwt.sign(
{ userId: user.user_id, email: user.email },
process.env.JWT_SECRET || "secret", // 환경 변수에서 비밀 키 가져오기
{ expiresIn: "1h" } // 토큰 만료 시간 설정
);
// 토큰과 초기 설정 완료 여부를 응답으로 반환
return res.status(200).json({ token, isInitialSetupComplete: user.isInitialSetupComplete });
} catch (error) {
// 서버 오류 처리
return res.status(500).json({ message: "Internal server error" });
}
}
- 사용자가 유효하면 JWT 토큰을 생성하여 응답으로 반환합니다. (설명 : 주석 참고)
2. 현재 로그인된 유저 정보 가져오기
import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "../../../lib/prisma";
import jwt from "jsonwebtoken";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// 요청 헤더에서 JWT 토큰 추출
const token = req.headers.authorization?.split(" ")[1];
// 토큰이 없는 경우 401 응답
if (!token) {
return res.status(401).json({ message: "Unauthorized" });
}
try {
// 토큰 검증 및 디코딩
const decoded: any = jwt.verify(token, process.env.JWT_SECRET || "secret");
// 데이터베이스에서 사용자 찾기
const user = await prisma.users.findUnique({
where: { user_id: decoded.userId },
});
// 사용자가 존재하지 않는 경우 404 응답
if (!user) {
return res.status(404).json({ message: "User not found" });
}
// 사용자 프로필 찾기
const userProfile = await prisma.userProfile.findUnique({
where: { user_id: decoded.userId },
});
// 사용자와 프로필 정보를 응답으로 반환
return res.status(200).json({ user, userProfile });
} catch (error) {
// 서버 오류 처리
return res.status(500).json({ message: "Internal server error" });
}
}
(설명 : 주석 참고)
요약
- 프론트엔드
- 사용자가 이메일과 비밀번호를 입력하여 로그인 요청을 보냅니다.
- 성공 시 JWT 토큰을 로컬 저장소에 저장하고, 초기 설정 여부에 따라 페이지를 이동합니다. - 백엔드
- 로그인 API 엔드포인트에서 이메일과 비밀번호를 확인하고, 유효한 경우 JWT 토큰을 발급합니다.
- JWT 토큰을 사용하여 현재 로그인된 유저 정보를 가져오는 API 엔드포인트를 구현합니다.