Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs-mstore.faisalaffan.com/llms.txt

Use this file to discover all available pages before exploring further.

Authentication Flow

Dokumentasi lengkap tentang alur autentikasi di MStore Dashboard, termasuk login, token refresh, dan logout.

Overview

MStore Dashboard menggunakan JWT authentication dengan strategi keamanan tinggi:
  • Access Token: Disimpan di memory (Pinia store)
  • Refresh Token: HttpOnly cookie (set oleh backend)

Login Flow

Login Implementation

// features/01_core/composables/useAuth.ts
export const useAuth = () => {
  const store = useAuthStore()
  const router = useRouter()
  const route = useRoute()

  const login = async (credentials: LoginCredentials) => {
    store.isLoading = true

    try {
      const response = await $fetch<LoginResponse>('/api/v1/auth/login', {
        method: 'POST',
        body: credentials,
        credentials: 'include', // Important for cookies
      })

      // Store access token in memory
      store.setToken(response.access_token)
      store.setUser(response.user)

      // Redirect to intended page or dashboard
      const redirectUrl = route.query.redirect as string
      router.push(redirectUrl || '/')

      return response
    } catch (error) {
      throw error
    } finally {
      store.isLoading = false
    }
  }

  return { login }
}

Token Refresh Flow

Token Refresh Implementation

// plugins/auth.client.ts
export default defineNuxtPlugin(() => {
  const authStore = useAuthStore()
  let refreshPromise: Promise<string> | null = null

  const apiFetch = $fetch.create({
    async onResponseError({ response, options }) {
      if (response.status !== 401) throw error

      // Prevent refresh loop
      if (options.url?.includes('/auth/refresh')) {
        authStore.logout()
        throw new Error('Session expired')
      }

      // Deduplicate refresh requests
      if (!refreshPromise) {
        refreshPromise = refreshToken()
      }

      try {
        const newToken = await refreshPromise
        // Retry original request
        return $fetch(response.url, {
          ...options,
          headers: {
            ...options.headers,
            Authorization: `Bearer ${newToken}`,
          },
        })
      } catch {
        authStore.logout()
        navigateTo('/login')
        throw error
      } finally {
        refreshPromise = null
      }
    },
  })

  const refreshToken = async (): Promise<string> => {
    const response = await $fetch<{ access_token: string }>(
      '/api/v1/auth/refresh-token',
      {
        method: 'POST',
        credentials: 'include',
      }
    )
    authStore.setToken(response.access_token)
    return response.access_token
  }

  return { provide: { api: apiFetch } }
})

Logout Flow

Logout Implementation

// features/01_core/store/auth.ts
export const useAuthStore = defineStore('auth', () => {
  const accessToken = ref<string | null>(null)
  const user = ref<User | null>(null)
  const isAuthenticated = ref(false)

  const logout = async () => {
    try {
      // Notify backend to invalidate refresh token
      await $fetch('/api/v1/auth/logout', {
        method: 'POST',
        credentials: 'include',
      })
    } catch {
      // Ignore errors - proceed with local logout
    } finally {
      // Clear local state
      accessToken.value = null
      user.value = null
      isAuthenticated.value = false

      // Redirect to login
      navigateTo('/login')
    }
  }

  return { accessToken, user, isAuthenticated, logout }
})

Route Protection Flow

Route Guard Implementation

// middleware/auth.global.ts
export default defineNuxtRouteMiddleware(async (to) => {
  const authStore = useAuthStore()

  // Public routes
  const publicRoutes = ['/login', '/register', '/forgot-password']
  if (publicRoutes.includes(to.path)) {
    // Redirect authenticated users away from auth pages
    if (authStore.isAuthenticated) {
      return navigateTo('/')
    }
    return
  }

  // Protected routes - require authentication
  if (!authStore.isAuthenticated) {
    return navigateTo({
      path: '/login',
      query: { redirect: to.fullPath },
    })
  }

  // Check token expiration
  if (authStore.isTokenExpired) {
    try {
      await refreshToken()
    } catch {
      return navigateTo('/login')
    }
  }
})

Session Recovery Flow

Session Recovery Implementation

// plugins/auth.client.ts
export default defineNuxtPlugin(async () => {
  const authStore = useAuthStore()

  // Try to recover session on app init
  if (authStore.isAuthenticated) {
    try {
      const response = await $fetch<{ access_token: string; user: User }>(
        '/api/v1/auth/refresh-token',
        {
          method: 'POST',
          credentials: 'include',
        }
      )

      authStore.setToken(response.access_token)
      if (response.user) {
        authStore.setUser(response.user)
      }
    } catch {
      // Session invalid - clear state
      authStore.logout()
    }
  }
})

Complete Auth State Machine

Security Considerations

  • Access Token: Memory only (not localStorage)
  • Refresh Token: HttpOnly cookie
  • Never expose tokens to JavaScript
  • Access Token: 15-30 minutes
  • Refresh Token: 7-30 days
  • Auto-refresh before expiration
  • All auth requests over HTTPS
  • Secure flag on cookies
  • SameSite=Strict atau Lax
  • Refresh token dalam HttpOnly cookie
  • Origin validation di backend
  • SameSite cookie attribute

Error Handling

ErrorUser ActionSystem Action
Invalid credentialsShow error messageClear form
Token expiredNone (auto-refresh)Refresh token
Refresh failedRedirect to loginClear all tokens
Network errorShow retry optionRetry with backoff
Account lockedShow support contactLog attempt

Next Steps

Core Authentication

Implementasi detail auth

API Interceptors

Auto-refresh implementation