Vue SDK: Technical Design Document
Version: 1.0
Date: December 2025
Author: Keith Nickas
Executive Summary
is a lightweight, type-safe Vue 3 SDK that bridges frontend and backend authentication systems. It manages secure session initialization, token caching, and metadata synchronization through a cookie-based authentication flow, enabling developers to implement enterprise-grade authentication with minimal boilerplate.@wristband/vue-client-auth
1. Problem Statement
Modern Vue 3 applications require robust authentication handling but face several challenges:
- Session Management Complexity: Managing authenticated sessions, tokens, and user metadata across the application requires considerable boilerplate
- Token Lifecycle Management: Implementing token caching, expiration detection, and refresh strategies is error-prone
- State Consistency: Ensuring consistent authentication state across components and managing side effects is tedious
- CSRF Protection: Implementing CSRF token handling for secure API communication requires careful setup
- Type Safety: Authentication hooks must provide first-class TypeScript support for custom session metadata
Solution: A composable-first authentication SDK that abstracts these concerns through reactive Vue composables powered by Pinia state management.
2. Architecture Overview
2.1 High-Level Architecture
2.2 Core Components
WristbandAuthStore (Pinia Store)
Central reactive state container managing:
- Auth Status Lifecycle: LOADING → AUTHENTICATED/UNAUTHENTICATED
- Session State: userId, tenantId, metadata
- Token Management: caching, TTL validation, refresh logic
- Configuration: URLs, CSRF settings, callbacks
- Error State: Centralized error tracking
Composables Layer
Adapter pattern implementations providing component-level access:
- Auth status and configurationuseWristbandStore()
- Session data with type genericsuseWristbandSession()
- Token acquisition and cache managementuseWristbandToken()
API Client
Fetch-based HTTP client with:
- CSRF token extraction and injection
- Cookie-based credential handling
- Standardized error responses
- Type-safe response handling
Auth Utils
Helper functions for:
- URL validation and construction
- Session metadata transformation
- Login/logout redirect orchestration
- Token expiration detection
3. Key Design Decisions
3.1 Pinia Store Pattern
Decision: Use Pinia's composition API pattern with defineStore('name', () => {...})
Rationale:
- Modularity: Store logic encapsulated in a single function (not spread across actions/mutations)
- Composability: Direct composition function syntax aligns with Vue 3 Composition API
- Type Safety: Implicit typing from ref/computed return values without additional type definitions
- Reactivity: Automatic dependency tracking with Vue 3 reactivity system
- Testability: Pure functions easier to mock and test in isolation
Trade-offs:
- Requires understanding of Vue reactivity internals
- Less boilerplate than Options API, but more flexible patterns
3.2 Composable-First API Design
Decision: Expose store functionality exclusively through composables, not direct store access
Rationale:
- Abstraction: Consumers depend on composable interface, not store implementation details
- Consistency: Standardized access pattern across the SDK (all hooks return a subset of store)
- Evolution: Internal store structure changes don't affect consumer code
- Type Narrowing: Composables can specialize types (e.g.,
)useWristbandSession<TSessionData>
Example:
// ✓ Recommended - Composable interface
const { metadata } = useWristbandSession<UserMetadata>()
// ✗ Avoid - Direct store access (not exported)
const store = WristbandAuthStore() // implementation detail
3.3 Token Caching with TTL and Retry Logic
Decision: Implement client-side token caching with:
- 30-second expiration buffer before actual token expiration
- Request deduplication using Promise references
- Exponential backoff retry (3 attempts, 100ms delay)
Rationale:
- Performance: Reduce token endpoint hits for rapid API calls
- Resilience: Handle transient network failures gracefully
- Security: Buffer time prevents token edge-case usage
- Race Condition Prevention: Shared promise prevents concurrent token requests
Implementation:
const tokenRequestRef = ref<Promise<string> | null>(null)
async function getToken(): Promise<string> {
// Return cached token if valid (with 30s buffer)
if (accessToken.value && accessTokenExpiresAt.value > Date.now() + 30000) {
return accessToken.value
}
// Deduplicate: return existing request if in-flight
if (tokenRequestRef.value) {
return tokenRequestRef.value
}
// Acquire token with retry logic
tokenRequestRef.value = acquireTokenWithRetry()
return tokenRequestRef.value
}
3.4 CSRF Protection Integration
Decision: Client-side extraction of CSRF token from cookies and injection into request headers
Rationale:
- Server-Driven: CSRF token managed entirely by backend
- Transport: Avoid token serialization - read directly from
document.cookie() - Flexibility: Configurable cookie/header names via
AuthConfig - Safety: Only applies to same-origin requests (fetch default behavior)
Cookie Extraction Pattern:
function getCsrfToken(cookieName: string): string | null {
const regex = new RegExp(`(^|;)\s*${cookieName}\s*=\s*([^;]+)`)
const match = document.cookie.match(regex)
return match?.[2] ?? null
}
3.5 Error Handling Strategy
Decision: Custom error classes
, WristbandError()
with specific error codesApiError()
Rationale:
- Specificity: Catch and handle specific error cases (
UNAUTHENTICATED,INVALID_TOKEN_URL, etc.) - Debugging: Error codes + messages + original response aid troubleshooting
- User Experience: Different errors warrant different handling (redirect vs. retry vs. alert)
- Typing: TypeScript discriminates error types for conditional handling
Usage Pattern:
try {
const token = await getToken()
} catch (error) {
if (error instanceof WristbandError) {
if (error.code === WristbandErrorCode.UNAUTHENTICATED) {
// Redirect to login
redirectToLogin()
}
}
}
4. Data Flow Diagrams
4.1 Initial Session Load Flow
4.2 Token Acquisition Flow (Cached)
4.3 Session State Transitions
5. Type Safety & Generics
5.1 Custom Session Metadata Typing
Pattern: Consumers define their session shape via TypeScript generics
// Define custom metadata structure
interface UserSession {
firstName: string
lastName: string
email: string
permissions: string[]
accountType: 'free' | 'pro' | 'enterprise'
}
// Typed composable usage
export default function Dashboard() {
const { metadata, userId } = useWristbandSession<UserSession>()
// Full type inference
const displayName = `${metadata.value.firstName} ${metadata.value.lastName}`
const isPro = metadata.value.accountType === 'pro'
}
5.2 Configuration Type Narrowing
AuthConfig type provides:
- Required fields*:
csrfCookieName(), csrfHeaderName(), loginUrl(), sessionUrl() - Optional fields:
tokenUrl(), transformSessionMetadata(), onSessionSuccess() - Methods:
(injected by store)getToken(), clearToken(), clearAuthData()
* CSRF functions
are being removed in the next version to simplify implementation.csrfCookieName(), csrfHeaderName()
6. Integration Example
Application Setup
// main.ts
import { createPinia } from 'pinia'
import { useWristbandStore } from '@wristband/vue-client-auth'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
// Initialize auth in root component
app.mount('#app')
// App.vue
<script setup lang="ts">
import { onMounted } from 'vue'
import { useWristbandStore } from '@wristband/vue-client-auth'
interface CustomMetadata {
department: string
role: string
}
const { authStatus, authError } = useWristbandStore({
csrfCookieName: 'X-CSRF-TOKEN',
csrfHeaderName: 'X-CSRF-TOKEN',
loginUrl: 'https://auth.example.com/login',
sessionUrl: '/api/auth/session',
tokenUrl: '/api/auth/token',
transformSessionMetadata: (raw) => ({
...raw,
department: raw.dept || 'unknown',
}),
})
onMounted(async () => {
try {
await store.fetchSession()
} catch (error) {
console.error('Session initialization failed:', error)
}
})
</script>
<template>
<div v-if="authStatus === 'loading'">
<LoadingSpinner />
</div>
<div v-else-if="authStatus === 'authenticated'">
<Dashboard />
</div>
<div v-else>
<LoginPrompt />
</div>
</template>
Protected API Calls
// hooks/useProtectedApi.ts
import { useWristbandToken } from '@wristband/vue-client-auth'
export function useProtectedApi() {
const { getToken } = useWristbandToken()
return {
async fetchUserData() {
const token = await getToken()
const response = await fetch('/api/user', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
credentials: 'include',
})
return response.json()
}
}
}
7. Testing Strategy
7.1 Unit Testing Approach
- Store Tests: Mock Pinia, test state mutations and action logic
- Composable Tests: Mock store, verify composables expose correct refs
- API Client Tests: Mock fetch, verify CSRF token handling and error states
- Utils Tests: Test pure functions (validation, URL parsing, token expiration)
7.2 E2E Testing
- Full flow: Configuration → Session fetch → Token acquisition → Protected API calls
- Error scenarios: Unauthorized responses, token endpoint failures, network errors
- Edge cases: Token expiration during request, CSRF token missing, invalid URLs
8. Performance Considerations
| Optimization | Implementation | Impact |
|---|---|---|
| Token Caching | In-memory storage with TTL + 30s buffer | Eliminates 90%+ of token endpoint requests |
| Request Deduplication | Shared Promise reference for in-flight requests | Prevents concurrent token acquisition |
| Lazy Config Validation | URL validation deferred until setConfig() | Reduces initialization overhead |
| Metadata Transformation | Optional hook applied during session fetch | Single transformation point, no reactivity overhead |
| CSRF Token Extraction | Regex parse of document.cookie | O(1) operation, no DOM traversal |
9. Security Considerations
9.1 Authentication Security
- Session Cookie: Server-side session cookie (HttpOnly, Secure, SameSite=Strict)
- CSRF Token: Extracted from cookie, injected into request headers
- Token Endpoint: Requires valid session cookie (backend validates)
- Token Expiration: Client-enforced with 30-second safety buffer
- Authorization: Bearer token scheme for authenticated API requests
9.2 Error Information Disclosure
- Error messages logged to console (development-friendly)
- Status codes exposed for error handling (necessary for app logic)
- Original Response object included for debugging
- No sensitive data in error messages
9.3 Dependencies & Vulnerabilities
- Minimal Dependencies: Only Pinia required (Vue 3 is peer dependency)
- Supply Chain: Regular updates via npm, CVE monitoring via Dependabot
- Type Safety: TypeScript compilation prevents many runtime vulnerabilities
10. Future Enhancements
| Feature | Rationale | Priority |
|---|---|---|
| Multi-tab Session Sync | Detect session changes across browser tabs | Medium |
| Remove CSRF Token | Simplifies implementation and reduces config requirements | High |
| Offline Mode | Cache session state, queue API calls during offline | Low |
| Session Refresh Hooks | Allow custom logic on token refresh | Medium |
| Audit Logging | Track auth events for security analysis | Low |
| Biometric Auth Support | Enable WebAuthn-based authentication | Low |
11. Conclusion
@wristband/vue-client-auth provides a production-ready authentication SDK built on Vue 3 fundamentals (Composition API, Pinia) with:
- Simplicity: Composable-first design reduces boilerplate
- Type Safety: Full TypeScript support with generic metadata typing
- Performance: Token caching and request deduplication
- Security: CSRF protection, session validation, error handling
- Extensibility: Metadata transformation, session success callbacks
The architecture prioritizes developer experience while maintaining enterprise-grade security and reliability standards.
Appendix: File Structure
src/
├── api/
│ ├── api-client.ts # Fetch-based HTTP client with CSRF
│ └── api-client.test.ts # Client unit tests
├── composables/
│ ├── useWristbandAuthStore.ts # Store access & config
│ ├── useWristbandSession.ts # Session data access
│ ├── useWristbandToken.ts # Token acquisition
│ └── *.test.ts # Composable tests
├── stores/
│ └── wristband.ts # Pinia store: main auth state
├── types/
│ ├── auth-store.ts # Core type definitions
│ ├── api-client-types.ts # HTTP types
│ ├── auth-utils-types.ts # Utility types
│ └── errors.ts # Error codes enum
├── utils/
│ ├── api-client-utils.ts # CSRF token extraction
│ ├── auth-store-utils.ts # URL validation, helpers
│ ├── auth-utils.ts # Redirect utilities
│ └── *.test.ts # Utility tests
├── error.ts # Error class definitions
└── index.ts # Public exports (entry point)