Vue.js Client Auth SDK
This project is in progress – An open source Vue.js SDK for integrating Wristband authentication and authorization features into Vue.js applications, e.g. useWristbandStore()
composable.

The Wristband Vue.js SDK provides authentication and authorization functionality for Vue.js applications, allowing developers to easily integrate Wristband's identity and access management features into their projects.
The SDK provides a set of components, composables, and services that can be used to handle user authentication, manage user sessions, and enforce access control based on user roles and permissions that are defined in the Wristband platform.
It simplifies the process of implementing secure authentication flows, such as login, logout, and token refresh, while also providing features like multi-factor authentication and social login options.
By using the Wristband Vue.js SDK, developers can ensure that their applications are secure and compliant with industry standards for identity and access management.
The SDK is designed to be easy to use and integrate, allowing developers to focus on building their applications while leveraging the robust authentication capabilities provided by Wristband.
The SDK is open source and available on GitHub, with comprehensive documentation and examples to help developers get started quickly.
Example App Configuration
Here is an example of the initialization code for the Wristband Vue.js SDK to configure authentication settings for a Vue.js application.
This code sets up the SDK with the necessary configuration options, such as the login URL, session URL, and token URL. It also initializes the Pinia state management library and mounts the Vue.js application to the DOM. The code ensures that the authentication state is properly managed and that the user session is fetched upon initialization.
import { createApp } from "vue";
import { createPinia } from "pinia";
import { WristbandAuthStore } from "@wristband/vue-client-auth"
import { router } from "./router";
import App from "./App.vue";
import './css/style.css';
const init = async () => {
// Create a Pinia instance
const pinia = createPinia();
const app = createApp(App);
// Install Pinia into the Vue application
app.use(pinia);
// Now we can set up the router and mount the app
app.use(router);
app.mount('#app');
// Access the WristbandAuthStore and attach it to Pinia
const wristbandAuth = WristbandAuthStore(pinia);
// Configure the WristbandAuthStore with your settings, e.g., API endpoints
await wristbandAuth.setConfig({
loginUrl: '/api/auth/login',
sessionUrl: '/api/v1/session',
tokenUrl: '/api/v1/token',
});
// Attempt to fetch the current session on app startup
await wristbandAuth.fetchSession();
};
// Initialize the app
init();
The Client Data Store
The SDK includes a client-side data store using Pinia for state management which manages the user session. It contains a baseline set of properties that are used to track the authentication state of the user, such as whether they are logged in or not, their access token, and their refresh token. The data store also includes methods for updating and retrieving these properties, as well as for handling token expiration and refresh.
import { defineStore } from 'pinia'
import { ref, computed, nextTick } from 'vue'
import { AuthStatus, TokenResponse, type SessionResponse, AuthConfig } from '../types/auth-store'
import apiClient from '../api/api-client'
import {
delay,
is4xxError,
resolveAuthProviderLoginUrl,
validateAuthProviderSessionUrl,
validateAuthProviderTokenUrl,
} from '../utils/auth-store-utils'
import { isUnauthorizedError } from '../utils/auth-utils'
import { WristbandError } from '../error'
import { WristbandErrorCode } from '../types/errors'
const TOKEN_EXPIRATION_BUFFER_TIME_MS = 30000
const MAX_API_ATTEMPTS = 3
const API_RETRY_DELAY_MS = 100
const WristbandAuthStore = defineStore('wristbandAuth', () => {
// State
const authError = ref<WristbandError | null>(null)
const isAuthenticated = ref(false)
const isLoading = ref(true)
const userId = ref('')
const tenantId = ref('')
const metadata = ref<Record<string, unknown>>({} as Record<string, unknown>)
const tokenUrl = ref('')
const accessToken = ref('')
const accessTokenExpiresAt = ref<number>(0)
const tokenRequestRef = ref<Promise<string> | null>(null)
// Config (set these before calling fetchSession)
const config = ref({
csrfCookieName: 'CSRF-TOKEN',
csrfHeaderName: 'X-CSRF-TOKEN',
disableRedirectOnUnauthenticated: false,
loginUrl: '',
sessionUrl: '',
tokenUrl: '',
transformSessionMetadata: undefined,
onSessionSuccess: undefined,
} as AuthConfig)
// Derived
const authStatus = computed(() =>
isLoading.value
? AuthStatus.LOADING
: isAuthenticated.value
? AuthStatus.AUTHENTICATED
: AuthStatus.UNAUTHENTICATED,
)
// Actions
async function setConfig(newConfig: Partial<typeof config.value>) {
config.value = { ...config.value, ...newConfig }
config.value.loginUrl = resolveAuthProviderLoginUrl(config.value.loginUrl)
validateAuthProviderSessionUrl(config.value.sessionUrl)
if (config.value.tokenUrl && config.value.tokenUrl.length > 0) {
validateAuthProviderTokenUrl(config.value.tokenUrl)
}
}
function updateMetadata(newMetadata: Record<string, unknown>) {
metadata.value = { ...metadata.value, ...newMetadata }
}
function clearToken() {
accessToken.value = ''
accessTokenExpiresAt.value = 0
tokenRequestRef.value = null
}
async function clearAuthData() {
isAuthenticated.value = false
isLoading.value = true
userId.value = ''
tenantId.value = ''
metadata.value = {}
// Reset token URL
clearToken()
await nextTick()
}
async function getToken(): Promise<string> {
const validatedTokenUrl = config.value.tokenUrl
if (!validatedTokenUrl || !validatedTokenUrl.trim()) {
throw new WristbandError(WristbandErrorCode.INVALID_TOKEN_URL, 'Token URL not configured')
}
if (!isAuthenticated.value) {
throw new WristbandError(WristbandErrorCode.UNAUTHENTICATED, 'User is not authenticated')
}
// Check if we have a valid cached token (with 30 second buffer)
if (
accessToken.value &&
accessTokenExpiresAt.value > Date.now() + TOKEN_EXPIRATION_BUFFER_TIME_MS
) {
return accessToken.value
}
// If there's already a token request in flight, return that promise
if (tokenRequestRef.value) {
return tokenRequestRef.value
}
const tokenRequest = (async () => {
let lastError: unknown
try {
for (let attempt = 1; attempt <= MAX_API_ATTEMPTS; attempt++) {
try {
const response = await apiClient.get<TokenResponse>(validatedTokenUrl, {
csrfCookieName: config.value.csrfCookieName,
csrfHeaderName: config.value.csrfHeaderName,
})
const { accessToken: newToken, expiresAt } = response.data
accessToken.value = newToken
accessTokenExpiresAt.value = expiresAt
return newToken
} catch (error: unknown) {
lastError = error
if (isUnauthorizedError(error)) {
accessToken.value = ''
accessTokenExpiresAt.value = 0
throw new WristbandError(
WristbandErrorCode.UNAUTHENTICATED,
'Token request unauthorized',
error,
)
}
if (is4xxError(error)) {
throw new WristbandError(
WristbandErrorCode.TOKEN_FETCH_FAILED,
'Failed to fetch token',
error,
)
}
if (attempt === MAX_API_ATTEMPTS) {
break
}
// Wait before retrying (only for 5xx errors and network issues)
await delay(API_RETRY_DELAY_MS)
}
}
// All attempts failed, throw the last error
throw new WristbandError(
WristbandErrorCode.TOKEN_FETCH_FAILED,
'Failed to fetch token after multiple attempts',
lastError,
)
} finally {
// Clear the token request reference
tokenRequestRef.value = null
}
})()
// Store the promise to prevent duplicate requests
tokenRequestRef.value = tokenRequest
return tokenRequest
}
async function fetchSession() {
const validatedSessionUrl = config.value.sessionUrl
let lastError: WristbandError | null = null
for (let attempt = 1; attempt <= MAX_API_ATTEMPTS; attempt++) {
try {
const response = await apiClient.get<SessionResponse>(validatedSessionUrl, {
csrfCookieName: config.value.csrfCookieName,
csrfHeaderName: config.value.csrfHeaderName,
})
const { userId: uid, tenantId: tid, metadata: rawMetadata, tokenUrl: tUrl } = response.data
if (config.value.onSessionSuccess) {
await Promise.resolve(config.value.onSessionSuccess(response.data))
}
if (rawMetadata) {
metadata.value = config.value.transformSessionMetadata
? config.value.transformSessionMetadata(rawMetadata as Record<string, unknown>)
: (rawMetadata as Record<string, unknown>)
}
tenantId.value = tid || ''
userId.value = uid || ''
isAuthenticated.value = true
isLoading.value = false
tokenUrl.value = tUrl || ''
await nextTick()
} catch (error: unknown) {
// Always bubble up invalid response errors (represents a dev configuration error)
if (error instanceof WristbandError) {
throw error
}
if (isUnauthorizedError(error)) {
lastError = new WristbandError(
WristbandErrorCode.UNAUTHENTICATED,
'User is not authenticated',
error,
)
break
}
// If it's a non-401 4xx error, bail early (don't retry client errors)
if (is4xxError(error)) {
lastError = new WristbandError(
WristbandErrorCode.SESSION_FETCH_FAILED,
'Failed to fetch session',
error,
)
break
}
// If this is the last attempt, don't delay
if (attempt === MAX_API_ATTEMPTS) {
lastError = new WristbandError(
WristbandErrorCode.SESSION_FETCH_FAILED,
'Failed to fetch session',
error,
)
break
}
await delay(API_RETRY_DELAY_MS)
}
}
if (config.value.disableRedirectOnUnauthenticated) {
authError.value = lastError
isAuthenticated.value = false
isLoading.value = false
}
}
return {
// State
isAuthenticated,
isLoading,
userId,
tenantId,
metadata,
tokenUrl,
// Derived
authStatus,
authError,
// Actions
clearAuthData,
clearToken,
getToken,
fetchSession,
setConfig,
updateMetadata,
// Config
config,
}
})
Learn More
To learn more about the Wristband Vue.js SDK, including installation instructions, configuration options, and usage examples, please visit the official GitHub repository and the wristband platform documentation.