728x90

Token-Based Authentication

JSON Web Tokens(JWT)을 이용하여 front-end auth solution을 만드는 방법을 알아보자.

사용할 모듈은 Router, Vuex, Axios가 되겠다.

 

Authentication 이 작동하는 방식

  1. Client가 Server에 Login 요청
  2. UserId와 password가 일치한다면 Server는 JWT 토큰 생성하여 Client에 반환
  3. Client는 Server로 부터 받은 JWT 토큰은 local storage에 저장
  4. 추후 Client에서 API 호출 시 HTTP request header에 localstorage에 저장된 JWT 토큰 실어서 전송
  5. Client가 로그아웃한다면 로그아웃 API 호출 및 localstorage에서 JWT 토큰 제거.

JWT 토큰 구성

  • 헤더 : Type(JWT), Hasing algorithm
  • Payload : Info we're transmitting. ex.) iss(issuer: 'verlopert.com'), exp(expiresIn: '7d', admin(admin:'false')
  • Signature : Hash of Header + Payload + Secret

VueJS Authentication 구현 과정

  1. Register users
  2. Login users
    • /register와 /login api는 userData({token,email,name})을 반환한다.
  3. Vuex
    • userData를 State에 저장한다.
    • userData를 localStorage에 저장한다.
    • token을 axios header default 설정으로 더해준다.
  4. Access protected data
  5. Logout
    • Vuex State로부터 userData를 지운다
    • localStorage로부터 userDAta를 지운다
    • axios header default 설정에 token을 지워준다

User Registration

과정

  1. RegisterUser.vue를 생성한다.
    • 유저 가입 정보(name,email,password)를 입력받는다.
    • //RegisterUser.vue
      ...
      methods:{
        register(){
          this.$store.dispatch('register',{
            name:this.name,
            email:this.email,
            password:this.password
          })
          .then(()=>{
            this.$router.push({name:'dashboard'});
          })
        }
      }
  2. Vuex
    • 유저 가입 정보를 서버로 전송한다. (/register api)
    • 반환된 userData(token,email,name)을 State와 localStorage에 저장한다.
    • Axios header에 토큰을 default 상태로 더해준다.
    • //store.js
      ...
      export default new Vuex.Store({
        state: {
          user:null
        },
        mutations:{
          SET_USER_DATA(state,userData){
            state.user = userData;
            localStorage.setItem('user',JSON.stringify(userData));
            axios.defaults.headers.common['Authorization'] = `Bearer ${userData.token}`
          }
        },
        actions:{
          register({commit}, credentials){
            return axios.post('//localhost:3000/register',credentials).then(
            ({data})=>{
              commit('SET_USER_DATA',data);
            })
          }
        }
      })

Login

과정

  1. RegisterUser.vue를 생성한다.
    • 유저 email, password를 입력받는다.
    • //LoginUser.vue
      ...
      methods:{
        login(){
          this.$store.dispatch('login',{
            email:this.email,
            password: this.password
          })
          .then(()=>{
            this.$router.push({name:'dashboard'})
          })
        }
      }
  2. Vuex 
    • 유저 가입 정보를 서버로 전송한다. (/login api)
    • 반환된 userData(token,email,name)을 State와 localStorage에 저장한다.
    • Axios header에 토큰을 default 상태로 더해준다.
    • //store.js
      ...
      export default new Vuex.Store({
        state: {
          user:null
        },
        getters:{
          loggedIn(state){
            return !!state.user;
          }
        }
        mutations:{
          ...
        },
        actions:{
          ...,
          login({commit}, credentials){
            return axios.post('//localhost:3000/login',credentaials)
            .then(({data})=>{
              commit('SET_USER_DATA',data);
            })
          }
        }
      })

Logout

  1. logout button과 logout method를 생성한다.
    • //AppNav.vue
      ...
      methods{
        logout(){
          this.$store.dispatch('logout')
        }
      }
  2. VueX
    • //store.js
      ...
      export default new Vuex.Store({
        state: {
          user:null
        },
        getters:{
          ...
        }
        mutations:{
          ...,
          CLEAR_USER_DATA(state){
            state.user = null;
            localStorage.removeItem('user');
            axios.default.headers.common['Authorization'] = null
          }
        },
        actions:{
          ...,
          logout({commit}){
            commit('CLEAR_USER_DATA')
          }
        }
      })
  3.  protected data(dashboard route) 접근 제한
    • logout 되더라도 dashboard page에 여전히 남아있다.
    • route에 Navigation Guard를 추가함으로써 해결할 수 있다.
    • //router.js
      const router = [
        ...
        {
          path:'/dashboard',
          name:'dashboard',
          component: Dashboard,
          meta: { requiresAuth: true},
        },
        ...
      ];
      
      router.beforeEach((to,from,next)=>{
        const loggedIn = localStorage.getItem('user');
        
        if(to.matched.some(record=>record.meta.requiresAuth) && !loggedIn){ // to.matched는 이동할 route와 match되는 route들
          next('/') // redirect to homepage
        }else{
          next() // direct to to
        }
      })
      
      export default router;

Error Handling

어떤 에러가 발생할 수 있을까?

  • User가 유효하지않은 id/password로 로그인 시도
  • 회원가입 시 이미 가입된 Email일때
  • 회원가입 시 password의 format이 유효하지않을때

Login Error Handling

//LoginUser.vue
...
methods:{
  login(){
    this.$store.dispatch('login',{
      email:this.email,
      password:this.password
    })
    .then(()=>{
      this.$router.push('dashboard');
    })
    .catch(err=>{
      this.error = err.response.data.error;
    })
  }
}

Register Error Handling

//RegiserUser.vue
...
methods:{
  register(){
    this.$store.dispatch('register',{
      name:this.name,
      email:this.email,
      password:this.password
    })
    .then(()=>{
      this.$router.push({name:'dashboard'});
    })
    .catch((err)=>{
      this.error = err.response.data.error
    })
  }
}

 

  • Client-side form validation 을 하고 싶다면 다음 Post에서 볼 수 있다.
  • component내에서 error handling을 유지하도록 하기위해 VueX내에서 error handling을 하지 않는다
    • 만약 여러 컴포넌트에서 사용하는 재사용 기능이라면 action내에서 state change를 위해 error handling을 해야할 것이다.
      만약 너의 에러가 컴포넌트에서 UI상 보여지는게 목적이라면 컴포넌트내에서 error handling을 해야할 것이다.

Automatic Login

페이지가 refresh 되더라도 로그인 상태를 유지하게 만들어보자.

//main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import state from './vuex/store'

Vue.config.productionTip = false;

new Vue({
  router,
  store,
  created(){ // created lifecycle hook 생성
    const userString = localStorage.getItem('user');
    if(userString){
      const userData = JSON.parse(userString);
      this.$store.commit('SET_USER_DATA',userData);
    }
  },
  render:h => h(App)
}).$mount('#app');

web 이 생성되면 localStorage를 보고 vuex에 다시 저장한다.

 

만약 누군가가 localStorage에 임의의 token을 집어넣어서 권한이 없는데도 접근하면 어쩌지?

localStorage에 강제로 값을 집어넣었을때 이를 detect할수 있는 client-side 보안 기능을 만들어보자

//main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import state from './vuex/store'
import axios from 'axios'

Vue.config.productionTip = false;

new Vue({
  router,
  store,
  created(){ // created lifecycle hook 생성
    const userString = localStorage.getItem('user');
    if(userString){
      const userData = JSON.parse(userString);
      this.$store.commit('SET_USER_DATA',userData);
    }
    axios.interceptors.respose.use(
      response=> response,
      error =>{
        if (error.response.status == 401){
          this.$store.dispatch('logout');
        }
        return Promise.reject(error)
      }
  },
  render:h => h(App)
}).$mount('#app');

localStorage의 token값이 잘못된 상태로 서버에 protected api를 요청하면 401에러를 받는데

401에러를 받으면 강제로 로그아웃 시켜버린다.

'VueJS' 카테고리의 다른 글

Vue3 google SEO 등록하기  (0) 2022.04.15
VueJS 3.0 form validating  (0) 2022.01.08
AWS 에 Jenkins와 Nginx 이용하여 vue project 올리기  (0) 2022.01.02
Vue 3 - 5. Props With Types  (0) 2021.12.28
Vue3 - 4.Custom type의 데이터  (0) 2021.12.28

+ Recent posts