2020. 5. 17. 22:39ㆍ컴퓨터언어/Node.js
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/
해보자
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."))
'컴퓨터언어 > Node.js' 카테고리의 다른 글
axios 400 POST error : Mongoose를 다룰 때 new 키워드를 조심하자 (0) | 2020.07.17 |
---|---|
[http] Callback (0) | 2020.05.18 |
[Passport] Cookie, Session을 활용하여 로그인 상태를 기억하는 웹페이지를 만들어보자 (0) | 2020.05.17 |
[Express Routing] Parameter를 이용한 URL 효율적 관리 (0) | 2020.05.12 |
[EJS] Scope (var - let - const의 차이) (0) | 2020.05.10 |