FrontEND/vue

JWT 기반 인증을 Vue + Axios + TypeScript로 구현하는 방법

mingmingIT 2025. 5. 7. 10:04

SPA(Vue) 환경에서 백엔드와 안전하게 인증을 연동하는 가장 일반적인 방식은 JWT (JSON Web Token) 기반 인증입니다.
Vue + Axios + TypeScript 조합에서는 이 JWT 인증 구조를 어떻게 구현하고 관리하는 것이 좋을까요?

이번 포스팅에서는 아래와 같은 순서로 JWT 기반 로그인 인증 흐름을 구축하는 방법을 실습 예제와 함께 소개합니다.


1. JWT 인증이란?

JWT는 로그인 성공 시 서버가 클라이언트에게 발급해주는 디지털 서명된 토큰입니다.
이 토큰을 클라이언트가 저장하고, 이후 서버에 요청을 보낼 때 Authorization 헤더에 포함시키면 서버는 이를 인증 수단으로 사용합니다.

  • ✅ 상태를 서버에 저장하지 않기 때문에 Stateless 인증이 가능
  • ✅ REST API와 궁합이 좋고 SPA에 많이 사용됨
  • ❗ 토큰 자체는 정보를 담고 있어 노출되면 위험 → 보안 고려 필수

2. 인증 흐름

  1. 사용자가 로그인 정보를 제출
  2. 서버가 토큰을 응답 (accessToken)
  3. 클라이언트는 토큰을 저장 (localStorage 또는 cookie)
  4. Axios 요청 시 Authorization 헤더에 토큰 자동 추가
  5. 로그아웃 시 저장된 토큰 삭제

3. Axios 인터셉터 구성 – 토큰 자동 첨부

// src/lib/axios.ts
import axios from 'axios'

const api = axios.create({
  baseURL: '/api',
  timeout: 5000,
})

api.interceptors.request.use((config) => {
  const token = localStorage.getItem('access_token')
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
})

export default api

💡 이렇게 하면 Axios 요청마다 Authorization 헤더를 자동으로 추가해줍니다.


4. 로그인 / 로그아웃 처리 – Pinia Store 활용

// src/stores/authStore.ts
import { defineStore } from 'pinia'
import api from '@/lib/axios'

interface LoginPayload {
  username: string
  password: string
}

export const useAuthStore = defineStore('auth', {
  state: () => ({
    token: localStorage.getItem('access_token') || '',
    isAuthenticated: !!localStorage.getItem('access_token'),
    error: '',
  }),

  actions: {
    async login(payload: LoginPayload) {
      try {
        const res = await api.post('/auth/login', payload)
        this.token = res.data.accessToken
        localStorage.setItem('access_token', this.token)
        this.isAuthenticated = true
        this.error = ''
      } catch (err: any) {
        this.error = err?.response?.data?.message || '로그인 실패'
        this.isAuthenticated = false
      }
    },

    logout() {
      this.token = ''
      this.isAuthenticated = false
      localStorage.removeItem('access_token')
    },
  },
})

5. 로그인 폼 컴포넌트 예제

<!-- src/views/LoginView.vue -->
<script setup lang="ts">
import { ref } from 'vue'
import { useAuthStore } from '@/stores/authStore'

const username = ref('')
const password = ref('')
const authStore = useAuthStore()

const handleLogin = async () => {
  await authStore.login({ username: username.value, password: password.value })
}
</script>

<template>
  <div>
    <h1>로그인</h1>
    <input v-model="username" placeholder="아이디" />
    <input v-model="password" placeholder="비밀번호" type="password" />
    <button @click="handleLogin">로그인</button>
    <p v-if="authStore.error" style="color: red">{{ authStore.error }}</p>
  </div>
</template>
 

6. 인증된 사용자만 접근 가능한 라우트 보호

// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '@/stores/authStore'

const routes = [
  { path: '/login', component: () => import('@/views/LoginView.vue') },
  {
    path: '/mypage',
    component: () => import('@/views/MyPageView.vue'),
    meta: { requiresAuth: true },
  },
]

const router = createRouter({
  history: createWebHistory(),
  routes,
})

router.beforeEach((to, from, next) => {
  const authStore = useAuthStore()
  if (to.meta.requiresAuth && !authStore.isAuthenticated) {
    next('/login')
  } else {
    next()
  }
})

export default router

✅ 라우터 가드에서 로그인 여부를 체크해서, 인증이 필요한 페이지는 로그인 후에만 접근할 수 있게 처리합니다.


7. 마무리 체크포인트

항목설명
🔐 인증 방식 JWT (access token only)
💾 저장소 localStorage 사용 (refresh token은 쿠키에 저장 고려)
🚫 로그아웃 처리 토큰 삭제 + 상태 초기화
🚨 에러 처리 로그인 실패 메시지 UI 표시
🔁 자동 로그인 유지 localStorage에서 token 읽어 초기 상태 구성

✅ 보안 측면 추가 고려 사항

  • XSS 공격 방지를 위해 가능한 경우 HttpOnly Cookie 방식 사용
  • Access Token이 만료되었을 때 자동으로 Refresh Token을 사용해 갱신하는 로직 추가
  • CSRF 대응을 위한 CORS 설정 및 서버 측 보안 검토

✍️ 마무리

SPA에서의 인증은 무조건 복잡한 것이 아니라, 구조를 정해놓고 구성하면 오히려 관리가 쉬워집니다.
위 예제를 기반으로 다음 단계에서는 다음과 같은 주제를 다루는 것이 좋습니다:

  • Refresh Token 기반 자동 로그인 갱신
  • 로그인 유지 UX 개선 (스피너, 알림 등)
  • 쿠키 기반 인증 vs LocalStorage의 보안 비교
  • JWT 토큰 페이로드 파싱 및 만료 체크