Wristband, an Identity Access Management Platform

Vue SDK: Technical Design Document

Version: 1.0
Date: December 2025
Author: Keith Nickas

Executive Summary

@wristband/vue-client-auth
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.

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:

  • useWristbandStore()
    - Auth status and configuration
  • useWristbandSession()
    - Session data with type generics
  • useWristbandToken()
    - Token acquisition and cache management

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:

Code:
// ✓ 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:

Code:
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:

Code:
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()
,
ApiError()
with specific error codes

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:

Code:
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

Code:
// 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:
    getToken(), clearToken(), clearAuthData()
    (injected by store)

* CSRF functions

csrfCookieName(), csrfHeaderName()
are being removed in the next version to simplify implementation.

6. Integration Example

Application Setup

Code:
// 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

Code:
// 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

OptimizationImplementationImpact
Token CachingIn-memory storage with TTL + 30s bufferEliminates 90%+ of token endpoint requests
Request DeduplicationShared Promise reference for in-flight requestsPrevents concurrent token acquisition
Lazy Config ValidationURL validation deferred until setConfig()Reduces initialization overhead
Metadata TransformationOptional hook applied during session fetchSingle transformation point, no reactivity overhead
CSRF Token ExtractionRegex parse of document.cookieO(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

FeatureRationalePriority
Multi-tab Session SyncDetect session changes across browser tabsMedium
Remove CSRF TokenSimplifies implementation and reduces config requirementsHigh
Offline ModeCache session state, queue API calls during offlineLow
Session Refresh HooksAllow custom logic on token refreshMedium
Audit LoggingTrack auth events for security analysisLow
Biometric Auth SupportEnable WebAuthn-based authenticationLow

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)

Loading Calendly...