학습목표
- 쿠키의 작동 원리를 이해할 수 있다
- 회원가입 및 로그인 등의 유저 인증에 대해 설명할 수 있다.
- 세션의 개념을 이해할 수 있다.
- 쿠키와 세션은 서로 어떤 관계이며, 각각이 인증에 있어서 어떤 목적으로 존재하는지 이해할 수 있다.
- 세션의 한계를 이해할 수 있다.
'Set-Cookie':[
'cookie=yummy',
'Secure=Secure; Secure',
'HttpOnly=HttpOnly; HttpOnly',
'Path=Path; Path=/cookie',
'Doamin=Domain; Domain=codestates.com'
]
<script>
태그로 접근 가능// client/App.js
import "./App.css";
import {BrowserRouter, Routes, Route} from "react-router-dom";
import Login from "./pages/Login";
import Mypage from "./pages/Mypage";
import {useEffect, useState} from "react";
import axios from "axios";
// 모든 요청에 withCredentials가 true로 설정됩니다.
axios.defaults.withCredentials = true;
function App() {
const [isLogin, setIsLogin] = useState(false);
const [userInfo, setUserInfo] = useState(null);
const authHandler = () => {
axios
.get("https://localhost:4000/userinfo")
.then((res) => {
setIsLogin(true);
setUserInfo(res.data);
})
.catch((err) => {
if (err.response.status === 401) {
console.log(err.response.data);
}
});
};
useEffect(() => {
authHandler();
}, []);
return (
<BrowserRouter>
<div className="main">
<Routes>
<Route
path="/"
element={
isLogin ? (
<Mypage setIsLogin={setIsLogin} setUserInfo={setUserInfo} userInfo={userInfo} />
) : (
<Login setIsLogin={setIsLogin} setUserInfo={setUserInfo} />
)
}
/>
</Routes>
</div>
</BrowserRouter>
);
}
export default App;
// client/Login.js
import React, {useState} from "react";
import axios from "axios";
export default function Login({setUserInfo, setIsLogin}) {
const [loginInfo, setLoginInfo] = useState({
userId: "",
password: "",
});
const [checkedKeepLogin, setCheckedKeepLogin] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const handleInputValue = (key) => (e) => {
setLoginInfo({...loginInfo, [key]: e.target.value});
};
const loginRequestHandler = () => {
if (!loginInfo.userId || !loginInfo.password) {
setErrorMessage("아이디와 비밀번호를 입력하세요");
return;
} else {
setErrorMessage("");
}
return axios
.post("https://localhost:4000/login", {loginInfo, checkedKeepLogin})
.then((res) => {
setIsLogin(true);
setUserInfo(res.data);
})
.catch((err) => {
if (err.response.status === 401) {
setErrorMessage("로그인에 실패했습니다.");
}
});
};
return (
<div className="container">
<div className="left-box">
<span>
Education
<p>for the</p>
Real World
</span>
</div>
<div className="right-box">
<h1>AUTH STATES</h1>
<form onSubmit={(e) => e.preventDefault()}>
<div className="input-field">
<span>ID</span>
<input type="text" data-testid="id-input" onChange={handleInputValue("userId")} />
<span>Password</span>
<input type="password" data-testid="password-input" onChange={handleInputValue("password")} />
<label className="checkbox-container">
<input type="checkbox" onChange={() => setCheckedKeepLogin(!checkedKeepLogin)} />
{" 로그인 상태 유지하기"}
</label>
</div>
<button type="submit" onClick={loginRequestHandler}>
LOGIN
</button>
{errorMessage ? (
<div id="alert-message" data-testid="alert-message">
{errorMessage}
</div>
) : (
""
)}
</form>
</div>
</div>
);
}
// client/MyPage.js
import axios from "axios";
import React from "react";
export default function Mypage({userInfo, setIsLogin, setUserInfo}) {
const logoutHandler = () => {
return axios
.post("https://localhost:4000/logout")
.then((res) => {
setUserInfo(null);
setIsLogin(false);
})
.catch((err) => {
alert(err);
});
};
return (
<div className="container">
<div className="left-box">
<span>
{`${userInfo.name}(${userInfo.userId})`}님,
<p>반갑습니다!</p>
</span>
</div>
<div className="right-box">
<h1>AUTH STATES</h1>
<div className="input-field">
<h3>내 정보</h3>
<div className="userinfo-field">
<div>{`💻 ${userInfo.position}`}</div>
<div>{`📩 ${userInfo.email}`}</div>
<div>{`📍 ${userInfo.location}`}</div>
<article>
<h3>Bio</h3>
<span>{userInfo.bio}</span>
</article>
</div>
<button className="logout-btn" onClick={logoutHandler}>
LOGOUT
</button>
</div>
</div>
</div>
);
}
// server/index.js
const express = require("express");
const cors = require("cors");
const logger = require("morgan");
const cookieParser = require("cookie-parser");
const fs = require("fs");
const https = require("https");
const controllers = require("./controllers");
const app = express();
// mkcert에서 발급한 인증서를 사용하기 위한 코드
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
const HTTPS_PORT = process.env.HTTPS_PORT || 4000;
app.use(logger("dev"));
app.use(express.json());
app.use(express.urlencoded({extended: false}));
app.use(cookieParser());
const corsOptions = {
origin: "http://localhost:3000",
methods: ["GET", "POST", "OPTIONS"],
credentials: true,
};
app.use(cors(corsOptions));
app.post("/login", controllers.login);
app.post("/logout", controllers.logout);
app.get("/userinfo", controllers.userInfo);
let server;
if (fs.existsSync("./key.pem") && fs.existsSync("./cert.pem")) {
const privateKey = fs.readFileSync(__dirname + "/key.pem", "utf8");
const certificate = fs.readFileSync(__dirname + "/cert.pem", "utf8");
const credentials = {
key: privateKey,
cert: certificate,
};
server = https.createServer(credentials, app);
server.listen(HTTPS_PORT, () => console.log(`🚀 HTTPS Server is starting on ${HTTPS_PORT}`));
} else {
server = app.listen(HTTPS_PORT, () => console.log(`🚀 HTTP Server is starting on ${HTTPS_PORT}`));
}
module.exports = server;
// server/login.js
const {USER_DATA} = require("../../db/data");
module.exports = (req, res) => {
const {userId, password} = req.body.loginInfo;
const {checkedKeepLogin} = req.body;
const userInfo = {
...USER_DATA.filter((user) => user.userId === userId && user.password === password)[0],
};
const cookiesOption = {
domain: "localhost",
path: "/",
httpOnly: true,
sameSite: "none",
secure: true,
};
if (!userInfo.id) {
res.status(401).send("Not Authorized");
} else if (checkedKeepLogin === true) {
cookiesOption.maxAge = 1000 * 60 * 30; //단위는 ms(밀리세컨드 === 0.001초), 30분동안 쿠키를 유지
//cookiesOption.expires = new Date(Date.now() + 1000 * 60 * 30); //지금 시간 + 30분 후에 쿠키 삭제
res.cookie("cookieId", userInfo.id, cookiesOption);
res.redirect("/userinfo");
} else {
res.cookie("cookieId", userInfo.id, cookiesOption);
res.redirect("/userinfo");
}
};
// server/logout.js
module.exports = (req, res) => {
const cookiesOption = {
domain: "localhost",
path: "/",
httpOnly: true,
sameSite: "none",
secure: true,
};
res.status(205).clearCookie("cookieId", cookiesOption).send("logout");
};