프론트/Vue

[Vue] Auth 적용, axios default header 추가, plugin 추가

ZestLee 2022. 2. 6. 21:43

2020-04 에 작성된 글입니다.

NUXT universal mode에 있는 에러입니다. 참고해 주세요!!

저의 기존 로그인, 인증 방식은 이렇습니다.

1. 로그인

로그인 요청 > 백앤드에 post 요청 (store)> 응답 온 토큰을 쿠키에 저장

 

2. 인증

쿠키에 있는 토큰을 꺼내어 store의 action을 실행 (middleware)> axios의 default header에 넣어 줌 (store)

페이지 이동 혹은 새로고침 할 때마다 middleware가 동작하기 때문에, 2번 인증은 매번 동작합니다.

이 방법을 여럿 사용하고 있으며, 튜토리얼에서도 많이 보실 수 있을 겁니다.

하지만, 저는 이 플로우를 통해 에러를 접하게 되었고, 그때부터 삽질을 하기 시작하였습니다….


axios 통신으로 data를 가져오기 위해 빈 array를 만들고, mouted에서 그 array에 data를 넣어 주는 방식으로 코딩하였습니다.

<script>
export default {
  data() {
    return {
      data: []
    }
  },
  mounted() {
    this.$axios.get(`/example/`).then((res) => {
      this.data = res.data
    })
  }
}
</script>

이 코딩은 페이지를 이동할 땐 잘 동작합니다. 하지만 새로고침하면 데이터들이 사라지는 에러가 발생합니다.

그 원인을 찾아 본 결과, store에서 default header를 넣어 준 부분에 문제가 있음을 발견하였습니다.

 

여러분들은 문제를 발견하셨나요…?

바로 this.$axios… 이 부분에 문제가 있었습니다.

저는 Nuxt 프로젝트를 universal mode로 작성 중이기 때문에, middleware가 새로고침 시 server 측에서, 페이지 이동 시 client 측에서 작동합니다.

여기에서 문제가 있었습니다. set_token 함수에서 셋팅해 준 this.$axios는, 새로고침 시에는 server의 axios, 라우트 이동 시에는 client의 axios였던 것입니다.

그리고, mounted에서 GET 요청을 한 this.$axios는 client 측의 axios였던 것이고요.

이게 무슨 말이냐고요…? 작성 중인 저도 헷갈리네요.

 

1. 라우트 이동 시 (페이지 이동)

client 측 middleware 작동 > client 측 axios에 default header 추가 > client 측에서 axios GET 요청

 

2. 새로고침 시

server 측 middleware 작동 > server 측 axios에 default header 추가 > client 측에서 axios GET 요청

결국 1번은 헤더에 토큰이 있기 때문에 정상 작동했고, 2번은 헤더에 토큰이 없기 때문에 권한 에러가 났던 것입니다.

콘솔에서 해당 내용을 확인해 볼까요?

<script>
export default {
  created() {
    if (process.server) {
      console.log(this.$axios.defaults.headers.common['X-Token'], 'server')
    }
    if (process.client) {
      console.log(this.$axios.defaults.headers.common['X-Token'], 'client')
    }
  }
}
</script>

created는 서버와 클라이언트 모두 동작하기 때문에, created에서 확인하겠습니다. (created와 beforeCreate를 제외한 모든 기본 옵션은 클라이언트에서만 작동합니다.)

페이지 이동 시
새로고침 시

따라서, 해당 플로우를 계속 사용할 수 없었습니다….

그리고, 아이디어가 떠올랐습니다.

 

1. 서버에서 작동하는 asyncData 혹은 created를 사용하면 되지 않을까? 그럼 서버의 토큰을 그대로 사용할 수 있잖아…!

둘 중에 무엇을 사용할지 고민하던 결과, 저는 asyncData를 사용하기로 했습니다. asyncData는 새로고침 시 server에서 작동하고, 라우트 이동 시 client에서 작동하기 때문에 지금 상황에 알맞다고 생각했습니다.

asyncData는 컴포넌트가 생성되기 전에 호출되기 때문에, this를 사용할 수 없습니다. 그래서 코딩하기 까다로운 부분이 있습니다. 하지만, 저는 이 길이 유일하다고 생각하여 만들어냈습니다.

하지만, 만들고 보니 한 가지 찝찝한 부분이 있었습니다.

제가 굳이 spa가 아닌 universal mode를 선택한 이유는 데이터를 서버에서 렌더링하기 위해서였고, 렌더링을 해야 하는 이유는 대회 목록이 검색 엔진에 걸리도록 하기 위함이었습니다.

하지만, 지금 데이터를 갖고 오는 부분은 myPage 사용자의 정보입니다. 이 부분은 검색 엔진에 걸릴 수 없기 때문에 서버에서 렌더링을 할 필요가 없습니다.

그 결과, 현재 만들고 있는 페이지에는 asyncData, created 둘 다 맞지 않았습니다.

 

2. default header를 페이지에서 넣어 주면 100% 에러가 없을 것…!

저는 default header를 페이지에서 요청을 하기 전에 넣어 주면 에러가 없을 거라고 생각했습니다.

<script>
export default {
  data() {
    return {
      data: []
    }
  },
  mounted() {
    this.$axios.defaults.headers.commonn['X-Token'] = `token ${this.$store.state.authUser}`
    this.$axios.get(`/example/`).then((res) => {
      this.data = res.data
    })
  }
}
</script>
 
client측의 axios에 디폴트 헤더를 넣어 주기 때문에 에러가 없습니다.

하지만 이렇게 하면 매번 같은 코드를 집어넣어야 하기 때문에, 지저분하다고 생각했습니다.

따라서, 저는 mixins를 생각해냈습니다.

이렇게 해 주면 같은 코드를 반복하여 쓰지 않고, 페이지마다 해당 믹스인을 추가만 해 주면 적용되기 때문에 괜찮은 방법이라고 생각했습니다.

<script>
import examMixin from 'mixin/examMixin'
export default {
  mixins: [examMixin],
  data() {
    return {
      data: []
    }
  },
  mounted() {
    this.$axios.get(`/example/`).then((res) => {
      this.data = res.data
    })
  }
}
</script>

하지만, 매 페이지마다 import를 해 주고 mixin을 넣어 주는 것 또한 코드 낭비라고 생각되었기 때문에, 새로운 방법을 찾아 봤습니다.

3. middleware를 클라이언트에서 사용하도록 하면 안 될까…? 그렇다면 전역 믹스인은…? 혹시… plugin은…?!?!

저는 미들웨어를 항상 클라이언트에서만 작동하게 하는 방법을 찾아 보았습니다. 생각보다 관련 글들이 많아 기대하였지만, 희망고문일 뿐이었고, 온통 ‘It’s not possible.’ 로 끝났습니다. (그럴 거면 글을 왜 쓴 거야… ^^)

그래서 어쩔 수 없이 다시 믹스인으로 넘어갔고, 전역 믹스인을 쓰면 되겠다! 라는 생각을 하게 되었습니다.

그러나, 해당 글에서 보면 알 수 있듯이 전역 믹스인 사용을 두 번이나 주의하였고, 웬만하면 사용자 지정 옵션 처리에만 사용해야 한다고 합니다. 괜히 사용했다가 어마어마한 부작용만 볼 것 같아서 다시 후진했고, 결국 plugin을 찾게 되었습니다.

Nuxt 페이지는 한글화가 잘 안 되어있기 때문에 vue 링크도 같이 추가합니다. (물론 vue도 한글화는 잘 되어있지 않습니다. 킹갓 한글을 완벽히 지원하지 않다니… 열일해라…😂)

먼저 플러그인을 추가하기 위해 plugins 폴더에 axios.js라는 파일을 만들겠습니다.

그런 후, nuxt.config.js 에 가서 plugins 부분에 방금 만든 axios.js 를 추가해 주세요.

  plugins: [
    { src: '~/plugins/axios.js' }
  ],

그리고, plugin이 server 측과 client 측 모두 실행되는지 확인해 봐야겠죠? axios.js에 아래 내용을 추가하겠습니다.

if (process.server) {
  console.log('server test')
}
if (process.client) {
  console.log('client test')
}

실행해 보겠습니다.

확인되었습니다. 너무 감동적인 순간이네요.

새로고침 혹은 페이지를 이동할 때마다 플러그인에서 디폴트 헤더를 설정해 주거나, axios 요청이 들어올 때마다 플러그인에서 헤더를 설정해 주면 됩니다.

전자는 그 이벤트를 캐치하기 까다롭고, 후자의 코드가 깔끔할 것 같다고 생각되었기 때문에 후자를 선택하였습니다. axios.js 를 아래 내용으로 작성했습니다.

export default function({ $axios, store }) {
  $axios.onRequest((config) => {
    config.headers.common['X-Token'] = `token ${store.state.authUser}`
  })
}

axios 요청이 들어올 때마다 인터셉터를 실행하고, 헤더를 설정해 주는 것입니다.

많은 인터셉터 헬퍼가 있으니 참고해 주세요.

자, 그럼 실행해 볼까요?

정상적으로 실행되고, 데이터도 날아가지 않습니다. 성공입니다!

이제 middleware에서 default header를 적용해 주던 코드도 날리면 됩니다.

추가로, 저는 서버에서 axios 요청하는 경우는 토큰이 필요없는 경우이기 때문에, 플러그인이 client에서만 작동하는 옵션을 추가해 줬습니다.

{ src: '~/plugins/axios.js', mode: 'client' }

처음에 말씀드렸다 시피, 저는 이 방법이 가장 좋은 방법이라 장담드릴 수 없습니다. 하지만, 예전의 저와 같은 누군가에겐 정말 좋은 팁일 것이라 생각되어 공유해 드립니다.

댓글은 저에게 많은 도움이 됩니다!!! 👏 👏 👏