OAuth란 무엇일까

Ho Kim·2021년 5월 3일
0
post-thumbnail

1. OAuth?

요즘 어느 페이지를 들어가나 보이는 버튼이 있습니다.

바로 '소셜 계정으로 로그인' 입니다.

(이미지 출처 : https://developers.naver.com)

이 버튼을 통해 로그인 하게 되면 사이트가 직접 사용자를 인증하지 않고, 다른 사이트를 통해 우회적으로 사용자가 인증됩니다.

사이트에서 네이버로 이 사용자가 등록된 사용자가 맞는지 인증을 요청하면 네이버는 사용자 확인 후 해당 사이트에 접근 토큰을 넘겨줍니다.

이것이 바로 OAuth(Open Authorization) 입니다.

사용자가 클라이언트 웹 페이지에 비밀번호를 넘겨줄 필요가 없으므로 개발자는 비밀번호 보호에 대해 고려하지 않아도 된다는 장점이 있습니다.

2. OAuth 2.0과 1.0

OAuth 2.0은 1.0에서 알려진 보안문제를 개선한 버전입니다.

  1. 오픈 API 요청 시 클라이언트 인증 방법으로 서명 대신 HTTPS를 사용하도록 의무화하여 안전성을 높였습니다.

  2. 1.0에서는 웹 애플리케이션만 지원했지만 2.0에서는 다양한 유형의 클라이언트와 이를 고려한 권한 승인 방법을 정의하여 더 넓은 범위에서 사용할 수 있게 되었습니다.

  3. 접근 토큰 재발급을 위한 재발급 토큰(Refresh Token)이 도입되었습니다. 접근 토큰은 유출을 막기 위해 비교적 짧은 만료 시간을 가지는데, 재발급 토큰이 있다면 사용자가 다시 로그인했을 때 재발급 토큰을 이용해 접근 토큰을 다시 발급받을 수 있습니다.

3. node.js로 실습해보기

카카오를 이용한 OAuth 로그인을 만들어봅시다.

시작하기 전에, 다음 페이지를 참고해 개발자 등록과 사이트 등록을 마쳐주시기 바랍니다.

https://developers.kakao.com/docs/latest/ko/getting-started/app
실습에서는 http://localhost:3000 을 통해 접근하고, http://localhost:3000/auth/kakao/callback 를 Redirect URI로 사용할 예정입니다.

서버를 열어야 하니 express가 필요하고, OAuth 로그인을 하기 위한 passport와 express-session이 필요합니다. 그리고 따로 관리해야할 값을 설정하는 dotenv, html을 쉽게 만들기 위한 nunjucks도 설치합니다.

passport는 node용 인증 미들웨어입니다. 각 페이지에 알맞은 Strategy를 설정하고나면 간단하게 인증을 확인할 수 있습니다.

쿠키와 세션

둘 모두 사용자가 웹사이트에 방문할경우 정보를 저장하는 것이지만 저장되는 곳과 저장 기한이 다릅니다.

쿠키 : 사용자의 컴퓨터에 저장되는 키-값 정보파일. 만료일이 되면 컴퓨터에서 삭제됨.

세션: 서버에 저장되는 정보. 브라우저가 꺼지면 삭제됨

+) 세션도 쿠키를 사용합니다. 페이지를 이동할 때 서버는 세션id를 쿠키에 담고, 다음 페아지에서 세션 id로 데이터를 이어받습니다. 하지만 브라우저가 꺼지면 세션id는 쓸모없는 정보가 되므로 보안적인 측면에서 쿠키보다 안전합니다.

1) express 세팅하기


// ./App.js

const express = require("express");
const nunjucks = require("nunjucks");
const passport = require("passport");
const session = require("express-session");

class App {
  constructor() {
    this.app = express();
    
    this.app.use(express.static("./template"));
    this.app.use(require("./routes"));
    
    this.setViewEngine();
    this.setSession();
  }

  setSession() {
    this.app.use(
      session({
        secret: "hokim",
        resave: false,
        saveUninitialized: true,
        cookie: {
          maxAge: 2000 * 60 * 60,
        },
      })
    );
    this.app.use(passport.initialize());
    this.app.use(passport.session());
  }
  setViewEngine() {
    nunjucks.configure("template", {
      autoescape: true,
      express: this.app,
    });
  }
}

module.exports = new App().app;

setViewEngine과 static 폴더 설정은 nunjucks와 css를 사용하지 않는다면 필요하지 않습니다.

기능만 생각하면 express, 라우팅, 세션 설정만 해도 됩니다.

2) 서버 열기


// ./server.js
const app = require("./app.js");
const port = 3000;

const server = app.listen(port, function () {
  console.log("Express listening on port", port);
});

기본설정을 해둔 App.js 내부의 App을 가져와서 3000번 포트를 열어줍니다.
이렇게 해야 만든 페이지에 localhost:3000으로 접근 할 수 있습니다.

3) 라우팅 설정하기

'/'으로 접근하면 로그인 페이지(login.html)를 띄웁니다.
'auth/kakao'로 접근하면 카카오쪽으로 로그인을 넘깁니다.
'auth/kakao/callback'으로 응답을 받습니다.
성공응답이면 'auth/success', 실패 응답이면 'auth/fail'로 이동합니다.


// ./routes/index.js
const express = require("express");
const router = express.Router();

router.get("/", (req, res) => {
  res.render("./login.html");
});
router.use("/auth", require("./auth"));

module.exports = router;

인증 관련한것은 모두 auth뒤에 있으므로 일괄적으로 auth로 라우팅 시키기 위해 router.use("/auth", require("./auth"));를 사용했습니다.


// ./routes/auth/index.js

const express = require("express");
const router = express.Router();
const passport = require("../passport-kakao.js");

router.get("/kakao", passport.authenticate("kakao"));

router.get(
  "/kakao/callback",
  passport.authenticate("kakao", {
    successRedirect: "/auth/success",
    failureRedirect: "/auth/fail",
  })
);

router.get(
  "/success",
  (req, res, next) => {
    if (!req.isAuthenticated()) res.redirect("/");
    else next();
  },
  (req, res) => {
    res.render("success.html");
  }
);

router.get("/fail", (req, res) => {
  res.render("fail.html");
});

module.exports = router;

passport.authenticate("kakao")); 을 통해 카카오 로그인 페이지로 이동하고, 사전에 입력해둔 주소로 결과를 보내줍니다.

저는 결과를 받을 주소를 "/kakao/callback"으로 해두었습니다. 결과를 받은 뒤에 성공이면 successRedirect로 이동하고, 실패면 failureRedirect로 이동합니다.

성공하지 않은 경우에도 url을 통해 성공 페이지에 접근할 수 있습니다. 이를 막기 위해 req.isAuthenticated()을 체크해서 인증된 사용자인지 확인한 후, 인증되지 않았을 경우 카카오 로그인 버튼이 있는 페이지로 사용자를 이동시킵니다.
인증된 사용자인 경우에는 res.render("success.html");로 성공 페이지를 보여줍니다.

실패한 경우에는 res.render("fail.html");로 실패 페이지를 보여줍니다.

4) passport

라우팅 설정을 했으니 실제로 카카오측으로 로그인을 넘길 수 있도록 passport를 작성해봅시다.


// ./routes/passport-kakao.js

const express = require("express");
const passport = require("passport");
const dotenv = require("dotenv");
const KakaoStrategy = require("passport-kakao").Strategy;

dotenv.config();
passport.use(
  new KakaoStrategy(
    {
      clientID: process.env.KAKAO_KEY,
      callbackURL: "http://localhost:3000/auth/kakao/callback"
    },
    async (accessToken, refreshToken, profile, done) => {
      //console.log(profile);
      try {
        user = accessToken;
        console.log(user);
        console.log(refreshToken);
        return done(null, user);
      } catch (e) {
        console.log(e);
      }
    }
  )
);
passport.serializeUser((user, done) => {
  done(null, user);
});
passport.deserializeUser((user, done) => {
  done(null, user);
});

module.exports = passport;

.env 파일 안에 KAKAO_KEY의 값을 입력해 두었습니다. 그리고나서 dotenv.config();를 선언해주면 process.env.keyName으로 상수처럼 사용할 수 있습니다.

passport를 사용하기 위해서는 사용하려는 인증 사이트에 대한 Strategy가 필요합니다. 보통 passport-sitename 형식으로 되어있어 npm으로 다운받으면 됩니다.

Strategy를 지정할때는 passport.use를 사용합니다.
clientIDcallbackURL이 카카오 개발자 페이지에 적힌것과 다르다면 오류가 발생하므로 주의해야 합니다.
로그인이 실행되고 나면
async (accessToken, refreshToken, profile, done) => {}));
가 실행됩니다. 여기서 done에 유저 정보를 넣어 반환해야하는데, 이 경우에는 반환할 유저 정보가 없어서 그냥 access token을 넣었습니다.

done이란?

Passport는 자격을 확인 한 후 확인 콜백을 호출합니다. 이 확인 콜백이 done입니다.

1. 에러가 발생했을 경우(예 : 데이터베이스를 사용할 수없는 경우)에는 다음을 반환합니다.

 return done(err);


2. 자격 증명이 유효할 경우 done에 사용자 데이터를 담아 반환합니다.

  return done(null, user);


3-1. 자격 증명이 유효하지 않은 경우 사용자 대신을 false를 담아 반환합니다..

  return done(null, false);


3-2. 실패 이유를 나타내는 추가 정보 메시지를 제공 할 수도 있습니다.

  return done(null, false,
  { message: 'Incorrect password.'});`

+) 각 페이지 작성하기

기본적인 기능은 모두 끝났습니다!
이제는 로그인 페이지로 이동할 버튼과 성공페이지, 실패 페이지만 작성하면 됩니다.

nunjucks는 아주 편리한 툴입니다. html을 일일이 작성할 필요 없이 베이스 페이지를 하나 만들면 템플릿처럼 불러와서 쓸 수 있습니다.

예를들어 페이지의 기본 설정은 비슷한데 내용만 바꾸고 싶다면 nunjucks의 block을 활용하면 됩니다.


<!-- ./template/base.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" />
  <title>Document</title>
  <link rel="stylesheet" type="text/css" href="/my.css" />
</head>
<body>
  <div class="box">{% block content %}{% endblock %}</div>
</body>
</html>

/* ./my.css */
.kakao_btn {
  text-decoration: none;
  font-size: 1.5rem;
  color: rgb(49, 25, 11);
  padding: 20px;
  margin: 50px auto;
  border-radius: 10px;
  background-color: #fee500;
}
.box {
  display: flex;
}
.middle {
  display: inline-flex;
  margin: 70px auto;
}

이런 베이스 페이지를 바탕으로 {% block content %}{% endblock %}위치의 데이터만 바꿔서 쓸 수 있습니다. 자바에서의 상속처럼 extends를 사용합니다.


로그인 페이지를 만들어봅시다.


<!-- ./template/login.html -->
{% set title = "로그인 페이지"%} 
{% extends "base.html"%} 
  
{% block content -%}
<a class="kakao_btn" href="/auth/kakao">카카오로 로그인</a>
{% endblock %}

성공 페이지를 만들어봅시다.


<!-- ./template/success.html -->
{% set title = "로그인 성공"%} 
{% extends "base.html"%} 
  
{% block content -%}
<h2 class="middle">카카오로 로그인 성공!</h2>
{% endblock %}

실패 페이지를 만들어봅시다.


<!-- ./template/fail.html -->
{% set title = "로그인 실패"%} 
{% extends "base.html"%} 
  
{% block content -%}
<h2 class="middle">카카오로 로그인 실패...</h2>
{% endblock %}

순식간에 페이지 세개를 만들었습니다!

0개의 댓글