본문 바로가기

웹개발/NodeJs

[Node.js] Passport 로그인 구현 (express & vue)

개발환경: Windows10, VS Code

 

지난 포스팅에 이어서 진행한다.

( express & vue 연동 1 )

( express & vue 연동 2 )

 

Backend 작업


 

1. backend 프로젝트를 열고, 터미널에서 express-session과 passport, passport-local을 설치한다.

 

> npm i express-session passport passport-local

 

- passport는 express-session을 사용하므로 express-session도 설치해야한다.

- 유저의 아이디와 비밀번호를 통한 로그인을 구현할 것이므로 passport-local을 설치한다.
(그외에 strategy들을 추가 설치하면 구글, 페이스북, 네이버, 카카오 등을 이용한 로그인도 구현할 수 있다.
자세한건 www.passportjs.org 를 통해 확인한다.)

 

2. 여러가지 설정값에 대한 관리를 위해 dotenv 패키지도 설치한다.

 

> npm i dotenv

 

 

3. 프로젝트 폴더에 '.env' 파일을 생성하고, 쿠키 비밀키를 입력한다.

 

COOKIE_SECRET = (아무거나 입력)

 

 

4. app.js 파일을 열고 dotenv를 적용한다.

 

require('dotenv').config()

 

- dotenv의 config()가 호출되면 '.env' 파일의 설정값들이 process.env에 저장된다.
이후 process.env.COOKIE_SECRET 처럼 설정값들을 사용할 수 있다.

 

5. app.js에 express-session을 적용하면서 쿠키 비밀키를 설정해준다.

(cookieParser와 express-session은 동일한 쿠키 비밀키를 사용해야 한다.)

 

var session = require('express-session');

....

app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(session({
  resave: false,
  saveUninitialized: false,
  secret: process.env.COOKIE_SECRET,
  cookie: {
    httpOnly: true,
    secure: false,
  }
}));

 

 

6. passport 폴더를 만들고 index.js 파일을 만들어서 passport 설정을 위한 코드를 작성한다.

 

const LocalStrategy = require('passport-local').Strategy;
const users = require('../data/users.json');
exports.config = (passport) => {

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

    passport.deserializeUser((id, done) => {
        const result = users.filter((user) => user.id === id);
        if (result.length > 0) {
            done(null, result[0]);
        }
    });

    passport.use(new LocalStrategy({
        usernameField: 'id',
        passwordField: 'password',
    }, (id, password, done) => {

        const result = users.filter((user) => user.id === id);
        if (result.length > 0) {
            const user = result[0];
            if (password === user.password) {
                done(null, user);
            } else {
                done(null, false, { message: "비밀번호 틀림" });
            }
        } else {
            done(null, false, { message: "가입되지 않은 회원"});
        }
    }));
};

 

- 'data/user.json' : 아직 DB를 사용하지 않으므로 이전 시간에 만들어둔 json 파일을 임시 사용한다.

- passport.serializeUser() : 로그인을 하면 user 정보를 세션에 저장하기위해 호출된다.
    user의 모든 정보를 저장할 필요는 없으므로, id만 세션에 저장하도록 설정한다.

- passport.deserializeUser() : 매 요청시 호출되면서 세션에 저장된 정보(위 코드에서는 id)를 불러온다.
   그 id에 해당하는 user를 찾아서 done()에 넣어주면 req.user에 유저 정보가 저장된다.

- usernameField, passwordField: 로그인시 설정된 이름으로 req.body에서 값을 읽어온다.
(위 코드에서는 req.body.id를 username으로, req.body.password를 password로 인식하게 된다.)

 

7. app.js에 passport를 적용한다.

 

var passport = require('passport');
require('./passport').config(passport);  // passport 설정

....

app.use(passport.initialize()); // req에 passport의 설정값들 적용
app.use(passport.session()); // session 정보 저장 (req.session, req.user)

 

 

8. routes/login.js 파일을 열어서, 로그인 요청시의 처리를 작성한다.

 

const express = require('express');
const passport = require('passport');
const router = express.Router();

router.get('/', function(req, res, next) {
    if (req.isAuthenticated() && req.user) {
        return res.json({ user: req.user });
    }
    return res.json({ user: null });
});

router.post('/', function(req, res, next) {
    if (req.isAuthenticated()) {
        return res.redirect('/');
    }
    passport.authenticate('local', (authError, user, info) => {
        if (authError) {
            console.error(authError);
            return next(authError);
        }
        if (!user) {
            return res.json(info);
        }
        return req.login(user, (loginError) => {
            if (loginError) {
                console.error(loginError);
                return next(loginError);
            }
            return res.json({ user });
        });
    })(req, res, next);     // 미들웨어 호출
});

module.exports = router;

 

- get 요청시, 로그인중이라면 (passport의 deserializeUser()에서 저장한) req.user의 유저 정보를 보낸다.

- post 요청시, local 전략으로 로그인을 시도한다. 이때 사용되는 username과 password는 LocalStrategy에서 설정한대로 req.body.id와 req.body.password가 된다. 로그인 성공시 user 정보를 보내주고, 로그인 실패시 그 이유를 알기위해 info 값을 보내준다.

- passport.authenticate()가 반환하는 값은 미들웨어이므로 반드시 뒤에 (req, res, next)를 붙여서 호출해주어야 한다.

 

Frontend 작업


 

1. frontend 프로젝트를 열고, 터미널에서 vuex 모듈을 설치한다.

 

> npm i vuex

 

 

2. src/store.js 파일을 만들고, vuex로 유저 정보를 저장할 저장소를 생성한다.

 

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export const store = new Vuex.Store({
    state: {
        user: null
    },
    getters: {
        user: (state) => { return state.user; }
    },
    mutations: {
        setUser(state, user) { state.user = user; }
    },
});

 

 

3. src/main.js 파일을 열고, vue 인스턴스에 생성한 store를 추가한다. 

 

import { store } from './store';

....

new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
})

 

 

4. components/IndexPage.vue 파일을 열고, 코드를 작성한다.

 

<template>
    <div v-if="user">
        <h1>접속한 유저</h1>
        <p>아이디 : {{ user.id }}</p>
        <p>비밀번호 : {{ user.password }}</p>
        <p>이름 : {{ user.name }}</p>
    </div>
</template>

<script>
export default {
    created() {
        this.$http.get("/api/login")
            .then((res) => {
                const user = res.data.user;
                if (user) {
                    this.$store.commit("setUser", user);
                } else {
                    this.$router.push( { name: "LoginPage" });
                }
            })
            .catch((err) => {
                console.error(err);
            });
    },
    computed: {
        user() { return this.$store.getters.user; }
    }}
</script>

<style></style>

 

- created(): IndexPage 실행시 로그인을 체크한다. 만약 로그인되어 있다면 유저 정보를 store에 저장하고, 로그인되어 있지 않다면 LoginPage로 이동한다.

 

5. components/LoginPage.vue 파일을 만들고, 코드를 작성한다.

 

<template>
    <div>
        <form @submit.prevent="onSubmit">
            <p>아이디</p>
            <input type="text" name="id" v-model="id">
            <p>비밀번호</p>
            <input type="password" name="password" v-model="password">
            <p></p>
            <button>로그인</button>
        </form>
    </div>
</template>

<script>
export default {
    data() {
        return {
            id: '',
            password: '',
        }
    },
    methods: {
        onSubmit() {
            const id = this.id;
            const password = this.password;
            this.$http.post("api/login", { id, password, }, { "Content-Type": "application-json" })
                .then((res) => {
                    if (res.data.user) {
                        this.$store.commit("setUser", res.data.user);
                        this.$router.push( { name: "IndexPage" });
                    } else if (res.data.message) {
                        alert(res.data.message);
                    }
                })
                .catch((err) => {
                    console.error(err);
                });
        }
    }
}
</script>

<style></style>

 

- onSubmit(): 로그인 버튼 클릭시 호출되며, 입력한 아이디와 비밀번호를 백엔드에 전송한다. 백엔드의 Passport-local 에서는 req.body로부터 username과 password를 읽어오므로, 전송시 { "Content-Type": "application-json" } 옵션값을 주어야 한다. (req.params는 인식하지 않는다.)

 

6. router/index.js 파일을 열고 라우터를 설정한다.

 

import Vue from 'vue'
import Router from 'vue-router'
import IndexPage from '@/components/IndexPage'
import LoginPage from '@/components/LoginPage'

Vue.use(Router)

export default new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'IndexPage',
      component: IndexPage
    },
    {
      path: '/Login',
      name: 'LoginPage',
      component: LoginPage
    }
  ]
})

 

 

마무리


 

1. frontend 프로젝트에서 빌드한다.

 

frontend> npm run build

 

2. backend 프로젝트를 열고 서버를 실행한다.

 

backend> npm start

 

 

3. localhost:3000에 접속하여 확인한다.

 

 

 

 

 

로그인 구현 완료!