본문 바로가기

Node.js

Node.js(22) - front(회원가입 및 로그인), 카카오 로그인 기능 구현

728x90
반응형

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 모듈을 추가했기에 해당 파일만 다시 소개함!)

 

지난 블로그 글 : https://codetraveler-hwang.tistory.com/entry/Nodejs21-server-%EB%B0%8F-%ED%99%98%EA%B2%BD-%EC%84%B8%ED%8C%85-TDD-back-end-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EB%B0%8F-Login-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84

 

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;