1) Node.js
1-1) front 기능 구현 - 회원가입, 로그인
1-2) 카카오 로그인 기능 구현
1) Node.js
1-1) front 기능 구현 - 회원가입, 로그인
※ back-end 폴더 및 파일들은 지난번 블로그 글에 소개한 내용을 그대로 사용하고,
거기에 front-end 내용만 새로 만들어 추가함으로써 회원가입 및 로그인 기능을 완성함
(단, 지난 back-end 내용 중 app.js 파일에 cors, cookie-parser 모듈을 추가했기에 해당 파일만 다시 소개함!)
Node.js(21) - server 및 환경 세팅, TDD, back-end 회원가입 및 Login 기능 구현
1) Node.js 1-1) server 및 개발 환경 세팅, User 테이블 생성 1-2) TDD 1-3) back-end 회원가입 및 Login 기능 구현 1) Node.js 1-1) server 및 개발 환경 세팅, User 테이블 생성 $ npm init -y $ npm install express mysql2 sequelize d
codetraveler-hwang.tistory.com
back
app.js
// "app"이라는 변수(router, 미들웨어 부분을 포함)를 내보내기만 하는 역할
const express = require("express");
const cors = require("cors");
const cookieParser = require("cookie-parser");
const app = express();
const router = require("./routes");
// npm install cors cookie-parser
app.use(
cors({
origin: true,
credentials: true,
})
);
app.use(cookieParser());
app.use(express.json());
// router
app.use(router);
app.use((error, req, res, next) => {
res.status(500).send(error.message);
});
module.exports = app;
$ npm init -y
$ npm install express nunjucks axios cookie-parser
back-end로 데이터를 던져줄 때 "Axios"를 사용함!
front-end, back-end 모두 TCP 기반으로 HTTP를 사용하고 있음!
"form element"는 기본적으로 request body의 내용을 "query string"으로 만들어서 보내줌!
(즉, request header의 "Content-Type"이 "application/x-www-form-urlencoded"라는 의미이다!)
SSR(Server Side Rendering): front server에서 데이터까지 완전히 받고,
전달받은 데이터를 가지고 html을 완성시킨 뒤 완성된 html을 브라우저에 던져준 것을 의미함!
front
server.js
const express = require("express");
const app = express();
const nunjucks = require("nunjucks");
const cookieParser = require("cookie-parser");
const axios = require("axios");
const request = axios.create({
baseURL: "http://127.0.0.1:3000",
withCredentials: true,
});
app.set("view engine", "html");
nunjucks.configure("views", {
express: app,
});
app.use(cookieParser());
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static("public"));
app.use((req, res, next) => {
try {
const { token } = req.cookies;
const [header, payload, signature] = token.split(".");
const pl = JSON.parse(Buffer.from(payload, "base64").toString("utf-8"));
// console.log(pl);
req.user = pl;
} catch (e) {
} finally {
next();
}
});
app.get("/", (req, res) => {
console.log(req.user);
// console.log(req.cookies);
// payload를 뽑아온 뒤 해당 string 값을 "JSON.parse()"를 써서 객체로 만든 뒤 userid와 userpw 값을 가져옴
if (req.user === undefined) return res.render("index.html");
const { userid, username } = req.user;
res.render("index.html", {
userid,
username,
});
});
app.get("/signup", (req, res) => {
res.render("user/signup.html");
});
app.post("/signup", async (req, res) => {
console.log(req.body);
// POST 127.0.0.1:3000/users
const response = await request.post("/users", {
...req.body,
});
console.log(response);
const { userid, userpw, username } = response.data;
res.redirect(
`/welcome?userid=${userid}&username=${username}&userpw=${userpw}`
);
});
app.get("/welcome", (req, res) => {
const { userid, userpw, username } = req.query;
res.render("user/welcome.html", {
userid,
userpw,
username,
});
});
app.get("/signin", (req, res) => {
res.render("user/signin.html");
});
app.listen(3005, () => {
console.log("front server start");
});
views/index.html
{% extends 'layout/layout.html' %} {% block content %}
<ul>
<li><a href="#">About</a></li>
<li><a href="#">Board</a></li>
{% if userid %}
<li>
{{username}}님 환영합니다~~ <br />
<button id="logout">logout</button>
<script type="text/javascript">
const logout = document.querySelector("#logout");
logout.addEventListener("click", (e) => {
e.preventDefault();
document.cookie = "token=; expires=Thu, 01 Jan 1970 00:00:01 GMT";
location.href = "/";
});
</script>
</li>
{% else %}
<li>
<a href="/signin">Sign in</a>
</li>
{% endif %}
<li><a href="/signup">Sign up</a></li>
</ul>
<div>main page</div>
{% endblock %}
views/layout/layout.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<title>Document</title>
</head>
<body>
<h1><a href="/">로고</a></h1>
{% block content %} {% endblock %}
</body>
</html>
views/user/signup.html
{% extends 'layout/layout.html' %} {% block content %}
<h2>회원가입</h2>
<form method="post" action="/signup">
<ul>
<li>아이디 : <input type="text" name="userid" /></li>
<li>패스워드 : <input type="password" name="userpw" /></li>
<li>이름 : <input type="text" name="username" /></li>
</ul>
<button type="submit">회원가입</button>
</form>
{% endblock %}
views/user/welcome.html
{% extends 'layout/layout.html' %} {% block content %}
<h2>환영합니다~~</h2>
<div>
{{userid}} <br />
{{userpw}} <br />
{{username}}
</div>
<a href="/">main page로 이동</a>
{% endblock %}
views/user/signin.html
{% extends 'layout/layout.html' %} {% block content %}
<h2>로그인</h2>
<form name="loginfrm" id="loginfrm">
<input type="text" name="userid" placeholder="아이디를 입력해주세요" />
<input type="password" name="userpw" placeholder="패스워드를 입력해주세요" />
<button type="submit">로그인</button>
</form>
<script type="module" src="/js/module/login.js"></script>
{% endblock %}
public/js/lib/request.js
const request = axios.create({
baseURL: "http://127.0.0.1:3000",
withCredentials: true,
});
export default request;
public/js/module/login.js
import request from "/js/lib/request.js"; // http://localhost:3005/js/lib/request.js
console.log(axios);
console.log(request);
// submit 버튼을 누르면
// input box에 입력된 userid와 userpw의 value를 구해옴
// 해당 value를 가지고 브라우저에서 back-end로 요청을 보냄!
// axios를 통해 응답을 받음
// 이때 응답 내용에는 "token"이 들어 있으므로
// 우리는 Javascript로 cookie를 만들 수 있음!
const frm = document.querySelector("#loginfrm");
frm.addEventListener("submit", async (e) => {
try {
e.preventDefault();
const { userid, userpw } = e.target;
console.log(userid.value, userpw.value);
const response = await request.post("/auth", {
userid: userid.value,
userpw: userpw.value,
});
console.log(response);
console.log(response.data);
if (response.status === 200) {
// cookie 설정
// 브라우저의 저장소: 1)로컬 스토리지, 2)세션 스토리지, 3)cookie
// "로컬 스토리지"는 브라우저를 껐다 켜도 남아있는 반면, "세션 스토리지"는 브라우저를 껐다 키면 날아감!
document.cookie = `token=${response.data.token};`;
location.href = "/";
}
} catch (e) {
alert("아이디와 패스워드가 다름!!");
}
});
1-2) 카카오 로그인 기능 구현
OAuth 2.0
OAuth: 자신이 소유한 resource에 소프트웨어 application이 접근할 수 있도록 허용해 줌으로써
접근 권한을 위임해주는 개방형 표준 프로토콜
OAuth 2.0의 구성 요소
- resource 소유자 : API에 대한 권한을 가지고 있으며, 이를 위임할 수 있는 이용자
- 보호된 resource : resource 소유자가 접근하는 대상
- client : resource 소유자를 대신해 보호된 resource에 접근하는 소프트웨어 등
- 인가 서버 : resource에 접근할 수 있는 접근 token을 발급
카카오 로그인은 OAuth 2.0 기반의 소셜 로그인 서비스이다!
카카오 로그인은 "Content-type"이 "application/x-www-form-urlencoded"이기에
axios 요청을 보낼 때 "query string"으로 줘야 함!
kakao developers - 애플리케이션 추가 및 설정
우선 kakao developers 홈페이지에 들어가 본인 로그인을 한 뒤 "내 애플리케이션"에서 애플리케이션을 추가함
내 애플리케이션 > 앱 설정 > 플랫폼 에 들어가서 Web 플랫폼을 등록함
제품 설정 > 카카오 로그인 에 들어가서 활성화 설정의 상태를 ON으로 설정함
위의 카카오 로그인 페이지에서 스크롤하여 아래로 내린 후 Redirect URI를 설정함
이를 통해 권한 부여 코드를 응답 받은 후 Access Token을 요청할 때 이 URI로 요청을 보내게 됨
내 애플리케이션 > 앱 설정 > 앱 키에 REST API 키는 VS Code에서 프로그래밍 진행 시 필요함
내 애플리케이션 > 제품 설정 > 카카오 로그인 > 보안에 들어가면 Client Secret 코드가 있는데
이는 REST API의 보안을 강화하기 위해 사용됨
카카오 로그인 코드 구현
※ 아래 파일들 외에 나머지 front-end 코드는 이번 블로그 내용과 같고, back-end 코드는 지난 블로그 내용과 같다!
front
server.js
const express = require("express");
const app = express();
const nunjucks = require("nunjucks");
const cookieParser = require("cookie-parser");
const axios = require("axios");
const request = axios.create({
baseURL: "http://127.0.0.1:3000",
withCredentials: true,
});
app.set("view engine", "html");
nunjucks.configure("views", {
express: app,
});
app.use(cookieParser());
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static("public"));
app.use((req, res, next) => {
try {
const { token } = req.cookies;
const [header, payload, signature] = token.split(".");
const pl = JSON.parse(Buffer.from(payload, "base64").toString("utf-8"));
// console.log(pl);
req.user = pl;
} catch (e) {
} finally {
next();
}
});
app.get("/", (req, res) => {
console.log(req.user);
// console.log(req.cookies);
// payload를 뽑아온 뒤 해당 string 값을 "JSON.parse()"를 써서 객체로 만든 뒤 userid와 userpw 값을 가져옴
if (req.user === undefined) return res.render("index.html");
const { userid, username } = req.user;
res.render("index.html", {
userid,
username,
});
});
app.get("/signup", (req, res) => {
res.render("user/signup.html");
});
app.post("/signup", async (req, res) => {
console.log(req.body);
// POST 127.0.0.1:3000/users
const response = await request.post("/users", {
...req.body,
});
console.log(response);
const { userid, userpw, username } = response.data;
res.redirect(
`/welcome?userid=${userid}&username=${username}&userpw=${userpw}`
);
});
app.get("/welcome", (req, res) => {
const { userid, userpw, username } = req.query;
res.render("user/welcome.html", {
userid,
userpw,
username,
});
});
app.get("/signin", (req, res) => {
res.render("user/signin.html");
});
const HOST = "https://kauth.kakao.com";
const REST_API_KEY = "생략";
const REDIRECT_URI = "http://localhost:3000/oauth/kakao";
const CLIENT_SECRET = "생략";
app.get("/kakao/login", (req, res) => {
// kauth.kakao.com
// /oauth/authorize?client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}&response_type=code
const redirectURI = `${HOST}/oauth/authorize?client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}&response_type=code`;
res.redirect(redirectURI);
});
app.listen(3005, () => {
console.log("front server start");
});
views/index.html
{% extends 'layout/layout.html' %} {% block content %}
<ul>
<li><a href="#">About</a></li>
<li><a href="#">Board</a></li>
{% if userid %}
<li>
{{username}}님 환영합니다~~ <br />
<button id="logout">logout</button>
<script type="text/javascript">
const logout = document.querySelector("#logout");
logout.addEventListener("click", (e) => {
e.preventDefault();
document.cookie = "token=; expires=Thu, 01 Jan 1970 00:00:01 GMT";
location.href = "/";
});
</script>
</li>
{% else %}
<li>
<a href="/signin">Sign in</a>
<a href="/kakao/login">카카오 로그인</a>
</li>
{% endif %}
<li><a href="/signup">Sign up</a></li>
</ul>
<div>main page</div>
{% endblock %}
back
app.js
// "app"이라는 변수(router, 미들웨어 부분을 포함)를 내보내기만 하는 역할
const express = require("express");
const cors = require("cors");
const cookieParser = require("cookie-parser");
const app = express();
const qs = require("qs");
const router = require("./routes");
const axios = require("axios");
// npm install cors cookie-parser
app.use(
cors({
origin: true,
credentials: true,
})
);
app.use(cookieParser());
app.use(express.json());
// router
app.use(router);
const HOST = "https://kauth.kakao.com";
const REST_API_KEY = "생략";
const REDIRECT_URI = "http://localhost:3000/oauth/kakao";
const CLIENT_SECRET = "생략";
// controller에서 하나의 router가 될 영역
app.get("/oauth/kakao", async (req, res, next) => {
// (step 2) token 받기: 인가 코드를 가지고 "access_token"을 받아오는 부분
// console.log(req.query);
const { code } = req.query;
const host = `${HOST}/oauth/token`;
const headers = {
"Content-type": `application/x-www-form-urlencoded`,
};
const body = qs.stringify({
grant_type: "authorization_code",
client_id: REST_API_KEY,
redirect_uri: REDIRECT_URI,
code,
client_secret: CLIENT_SECRET,
});
const response = await axios.post(host, body, headers);
console.log(response.data); // 여기서는 token만 받아옴!!
// token을 가지고 회원정보를 조회해야 함!
// (step 3) 회원정보 가져오기
try {
const { access_token } = response.data;
const host = `https://kapi.kakao.com/v2/user/me`;
// body 정보는 필요없기에 "null"로 처리함!
const user = await axios.post(host, null, {
headers: {
"Content-type": "application/x-www-form-urlencoded",
Authorization: `Bearer ${access_token}`,
},
});
console.log(user);
// 우리 DB에 저장하는 것이 가장 좋다!
// user 정보를 우리 DB에 저장해놓고
// 형태가 다를 수 있어 우리 형태의 토큰으로 재발급함
} catch (e) {}
// front server에 redirect를 요청함
res.redirect("http://localhost:3005");
});
app.use((error, req, res, next) => {
res.status(500).send(error.message);
});
module.exports = app;
'Node.js' 카테고리의 다른 글
Node.js(24) - HTTP & WebSocket 정리, WebSocket 사용 방법(ws, socket.io) (0) | 2023.01.26 |
---|---|
Node.js(23) - Multer를 활용한 파일 업로드 (0) | 2023.01.25 |
Node.js(21) - server 및 환경 세팅, TDD, back-end 회원가입 및 Login 기능 구현 (0) | 2023.01.18 |
Node.js(20) - 로그인 기능(cookie, session), 암호화, JWT 개념 및 규격 (0) | 2023.01.17 |
Node.js(19) - N:M 관계(sequelize 활용), Board의 write/view 구현 (0) | 2023.01.16 |