[OAuth] 로그인 인증과 개인정보 관리는 대기업에 맡기자

2020. 5. 17. 22:39컴퓨터언어/Node.js

728x90
반응형

OAuth의 특징 3가지

(나의 새로운 웹서비스를 페이스북에 연결하는 것을 예로 든다면)

1. 페이스북에서 프로필 사진 또는 이메일, 친구목록 등 내가 원하는 다양한 정보를 세분화하여 가져올 수 있다.

2. 페이스북으로부터 읽기 전용 또는 페이스북에 쓰기까지 할 수 있다.

3. 페이스북에서는 언제든지 연결된 계정을 해제할 수 있다.

 

즉 OAuth를 활용하면 친구목록을 가져옴으로써 파생되는 다양한 기능을 이용할 수 있고, 개발자 입장에서도 관리가 편하다.


사용방법

1. 내 웹서비스를 완성한다.

2. 페이스북이나 깃허브 등 개발자 콘솔에 내 웹서비스 정보를 입력한다.

3. App ID(Clien ID)를 부여받는다.

4. 내가 필요로 하는 개인정보를 수집하는 데 동의하는 유저에 한해서 로그인 인증을 진행한다.

5. 유저가 페이스북에서 로그인을 성공하면 내 웹서비스에는 Auth Code(인증코드)가 도착한다.

6. 나는 그 인증코드를 가지고 페이스북에 Access Token(액세스 토큰)과 바꿔올 수 있다. 이 토큰은 내 DB에 저장한 후 계속해서 페이스북 서버에 접근해서 관련 정보를 열람할 수 있게 한다.

* Auth Code는 로그인을 위한 1회 입장권인 반면, Access Token은 로그인 뿐만 아니라 계속해서 그 계정의 기타 정보들에 접근할 수 있는 연간 회원권이라고 보면 된다.

 

http://www.passportjs.org/packages/passport-google-oauth20/

 

passport-google-oauth20

Google (OAuth 2.0) authentication strategy for Passport.

www.passportjs.org

 


해보자

 

1. Google Development Console에 가서 내 웹사이트를 등록한다.


2. 필요한 모듈을 설치한다.

findorcreate는 나의 DB에 Google 계정이 이미 저장되어 있으면 찾아오고, 없다면 새로 만들어주는 기능을 미리 구현해 둔 모듈이다.

npm i passport-google-oauth20 mongoose-findorcreate

3. 두 모듈을 require한다.

const GoogleStrategy = require('passport-google-oauth20').Strategy;
const findOrCreate = require('mongoose-findorcreate');

 

4. DB스키마를 만들고, 스키마에 findOrCreate를 연결한다.

const userSchema = new mongoose.Schema({
  googleId: String,
  email: String,
  password: String
});

userSchema.plugin(findOrCreate);

5. Google OAuth를 동작시키는 핵심 코드를 작성한다.

passport.serializeUser(function(user, done) {
  done(null, user.id);
});

passport.deserializeUser(function(id, done) {
  User.findById(id, function(err, user) {
    done(err, user);
  });
});

passport.use(new GoogleStrategy({
  clientID: process.env.CLIENT_ID,
  clientSecret: process.env.CLIENT_SECRET,
  callbackURL: "http://localhost:3000/auth/google/secrets",
  userProfileURL: "https://www.googleapis.com/oauth2/v3/userinfo"
},
function(accessToken, refreshToken, profile, cb) {
  User.findOrCreate({ googleId: profile.id }, function (err, user) {
    return cb(err, user);
  });
}
));

*clientID와 clientSecret은 Google Development Console에서 "사용자 인증정보"를 등록하면 발급받을 수 있다.

이는 개발자로서 매우 중요한 개인정보이므로, .env에 잘 저장해둔다.

callbackURL에는 Google 인증이 성공하면 리다이렉트 될 경로를 작성하며, 이는 사용자 인증정보 등록 시 입력한 것과 동일해야 한다.

userProfileURL은 실제 개인정보를 가져오는 Google API 주소다.


6. 로그인이 승인되면 리다이렉트 될 Route를 완성한다.

 

아래 코드는 유저가 Google 로그인 버튼을 누르면 구글로그인 페이지가 나타나도록 한다.

app.get("/auth/google",
  passport.authenticate("google", {scope: ["profile"]})
)

 

아래 코드는 유저가 위 구글로그인 페이지에서 인증이 성공하였는지에 따라 페이지를 분기처리하는 것이다.

예제에서는 인증이 성공하면 /secrets로 이동하도록 설정했다.

참고로 아래 코드에서 "/auth/google/secrets"는, 위 4번 사진처럼 Google Development Console에서와 동일하게 명시한 인증성공 직후 주소를 나타내며,

res.redirect("/secrets")는 그 인증성공 상태를 쿠키에 저장한 채로 리다이렉트하는 것이다.

 

app.get("/auth/google/secrets",
  // 구글 로그인을 사용한 인증방식을 채택하고, 인증이 실패하면 로그인 페이지로 튕기도록 설정한다.
  passport.authenticate("google", { failureRedirect: "/login" }),
  function(req, res) {
    // 인증에 성공하면 쿠키를 저장하고 secrets 페이지로 이동한다. 물론 루트("/")로 이동시키든 어디로 이동시키든 자유다.
    res.redirect("/secrets");
});

<완성 코드>

require("dotenv").config();
const express = require("express");
const bodyParser = require("body-parser");
require("ejs");
const mongoose = require("mongoose");
const session = require("express-session");
const passport = require("passport");
const passportLocalMongoose = require("passport-local-mongoose");
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const findOrCreate = require('mongoose-findorcreate');
const app = express();

app.use(express.static("public"));
app.use(bodyParser.urlencoded({extended: true}));
app.set("view engine", "ejs");

app.use(session({
  secret: "ThisSecretIsEqualToCustomKeyForEncryption.",
  resave: false,
  saveUninitialized: true,
}));

app.use(passport.initialize());
app.use(passport.session());

mongoose.connect("mongodb://localhost:27017/userDB", {useNewUrlParser: true, useUnifiedTopology: true});

mongoose.set('useCreateIndex', true);

const userSchema = new mongoose.Schema({
  googleId: String,
  email: String,
  password: String
});

userSchema.plugin(passportLocalMongoose)
userSchema.plugin(findOrCreate);

const User = new mongoose.model("User", userSchema);

passport.use(User.createStrategy());
 
passport.serializeUser(function(user, done) {
  done(null, user.id);
});

passport.deserializeUser(function(id, done) {
  User.findById(id, function(err, user) {
    done(err, user);
  });
});

passport.use(new GoogleStrategy({
  clientID: process.env.CLIENT_ID,
  clientSecret: process.env.CLIENT_SECRET,
  callbackURL: "http://localhost:3000/auth/google/secrets",
  userProfileURL: "https://www.googleapis.com/oauth2/v3/userinfo"
  },
  function(accessToken, refreshToken, profile, cb) {
    console.log(profile);
    
    User.findOrCreate({ googleId: profile.id }, function (err, user) {
      return cb(err, user);
    });
  }
));

app.get("/", function (req, res) {
  res.render("home")
});

app.get("/auth/google",
  passport.authenticate("google", {scope: ["profile"]})
);

app.get("/auth/google/secrets", 
  passport.authenticate("google", { failureRedirect: "/login" }),
  function(req, res) {
    // Successful authentication, redirect home.
    res.redirect("/secrets");
});


app.get("/secrets", function (req, res) {
  if (req.isAuthenticated()) {
    res.render("secrets");
  } else {
    res.redirect("/login");
  }
});

app.route("/register")
  .get(function (req, res) {
    res.render("register")
  })

  .post(function (req, res) {
    User.register({username: req.body.username}, req.body.password, function (err, newUser) {
      if (err) {
        console.log(err);
        res.redirect("/register");
      } else {
        // HTML 폼에서 POST를 보내는 과정, 즉 회원가입 과정에서 에러가 없는 경우에 한해 아래 로그인세션 기억 코드(passport.authenticate())가 실행된다.
        passport.authenticate("local") (req, res, function () {
          // 아래 콜백함수 코드는 회원가입에 이어 그 상태로 로그인까지 연속해서 인증성공했고 + 해당 인증 내용을 바탕으로 하는 로그인 상태 쿠키 생성 성공일 때 실행된다.
          res.redirect("/secrets")
        })
      }
    })
  });

app.route("/login")
  .get(function (req, res) {
    res.render("login")
  })

  .post(function (req, res) {
    const user = new User({
      username: req.body.username,
      password: req.body.password
    });

    req.login(user, function (err) {
      if (err) {
        // const user에 해당하는 정보로 로그인할 수 없을 경우 호출되는 부분
        console.log(err);
      } else {
        passport.authenticate("local") (req, res, function () {
          res.redirect("/secrets");
        })
      }
    })
  });

app.route("/logout")
  .get(function (req, res) {
    req.logout();
    res.redirect("/");
  });

app.listen(3000, () => console.log("Server is connected on port 3000."))
728x90
반응형