Skip to main content

Session Composables

Vue 3 composables for managing JWT-based sessions with automatic encryption, token refresh, and session lifecycle management.

Not available with publishable key

When your app uses a publishable key (typical frontend setup), all session composables are disableduseSessionStart, useSessionVerify, useSessionRefresh, useSessionRevoke, useSessionRevokeAll, useSessionList, useSessionAutoRefresh will throw. Sessions can only be started and managed on your backend. Your backend returns a session token to the frontend; the frontend must pass that token as session in the options for every Ductape request (e.g. useDatabaseQuery(..., { ...opts, session: token })). The examples below apply when using an access key (e.g. server-side Vue or a backend that uses the client with full auth).

useSessionStart

Create a new session with encrypted JWT token.

Basic Session Start

<script setup lang="ts">
import { useSessionStart } from '@ductape/vue';

const { mutate: startSession, isLoading, data, error } = useSessionStart({
onSuccess: (result) => {
localStorage.setItem('token', result.token);
localStorage.setItem('refreshToken', result.refreshToken);
console.log('Session started:', result.sessionId);
},
onError: (error) => {
alert('Failed to start session: ' + error.message);
}
});

const handleLogin = async (userId: string, email: string) => {
// After validating credentials, start the session
startSession({
tag: 'user-session',
data: {
userId,
email,
role: 'user'
},
identifier: userId
});
};
</script>

<template>
<div>
<button
@click="handleLogin('user-123', 'user@example.com')"
:disabled="isLoading"
>
{{ isLoading ? 'Starting Session...' : 'Log In' }}
</button>

<div v-if="data">
<p>Session ID: {{ data.sessionId }}</p>
<p>Expires: {{ new Date(data.expiresAt).toLocaleString() }}</p>
</div>

<p v-if="error" style="color: red">{{ error.message }}</p>
</div>
</template>

With Custom Expiration

<script setup lang="ts">
import { useSessionStart } from '@ductape/vue';

const { mutate: startSession, isLoading } = useSessionStart({
onSuccess: (result) => {
localStorage.setItem('token', result.token);
localStorage.setItem('refreshToken', result.refreshToken);
}
});

const handleStartExtendedSession = () => {
startSession({
tag: 'user-session',
data: {
userId: 'user-123',
email: 'user@example.com',
role: 'admin'
},
identifier: 'user-123',
expiresIn: 7 * 24 * 60 * 60 // 7 days in seconds
});
};
</script>

<template>
<button @click="handleStartExtendedSession" :disabled="isLoading">
Start 7-Day Session
</button>
</template>

Admin Session Example

<script setup lang="ts">
import { useSessionStart } from '@ductape/vue';

const { mutate: startSession, data } = useSessionStart({
onSuccess: (result) => {
localStorage.setItem('adminToken', result.token);
localStorage.setItem('adminRefreshToken', result.refreshToken);
}
});

const handleAdminLogin = () => {
startSession({
tag: 'admin-session',
data: {
userId: 'admin-456',
email: 'admin@company.com',
role: 'admin',
permissions: ['users.manage', 'content.edit', 'reports.view']
},
identifier: 'admin-456'
});
};
</script>

<template>
<button @click="handleAdminLogin">
Start Admin Session
</button>
</template>

useSessionVerify

Verify and decode a JWT session token.

Basic Token Verification

<script setup lang="ts">
import { useSessionVerify } from '@ductape/vue';
import { onMounted } from 'vue';

const { mutate: verifySession, data, isLoading, error } = useSessionVerify({
onSuccess: (result) => {
console.log('Session valid:', result.data);
console.log('Expires at:', new Date(result.expiresAt));
},
onError: (error) => {
// Token invalid or expired
localStorage.removeItem('token');
localStorage.removeItem('refreshToken');
}
});

onMounted(() => {
const token = localStorage.getItem('token');
if (token) {
verifySession({
tag: 'user-session',
token
});
}
});
</script>

<template>
<div v-if="isLoading">Verifying session...</div>

<div v-else-if="error">
Session expired. Please log in again.
</div>

<div v-else-if="data">
<h2>Welcome back!</h2>
<p>User ID: {{ data.data.userId }}</p>
<p>Email: {{ data.data.email }}</p>
<p>Role: {{ data.data.role }}</p>
<p>Session expires: {{ new Date(data.expiresAt).toLocaleString() }}</p>
</div>
</template>

Protected Route Component

<script setup lang="ts">
import { useSessionVerify } from '@ductape/vue';
import { useRouter } from 'vue-router';
import { onMounted, watch } from 'vue';

const router = useRouter();
const { mutate: verifySession, data, isLoading } = useSessionVerify();

onMounted(() => {
const token = localStorage.getItem('token');
if (token) {
verifySession({
tag: 'user-session',
token
});
}
});

watch([isLoading, data], ([loading, sessionData]) => {
if (!loading && !sessionData) {
router.push('/login');
}
});
</script>

<template>
<div v-if="isLoading">
Verifying access...
</div>
<div v-else-if="data">
<slot />
</div>
</template>

Role-Based Access Control

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useSessionVerify } from '@ductape/vue';

const hasAccess = ref(false);
const { mutate: verifySession, isLoading } = useSessionVerify({
onSuccess: (result) => {
if (result.data.role === 'admin') {
hasAccess.value = true;
} else {
alert('Admin access required');
}
}
});

onMounted(() => {
const token = localStorage.getItem('token');
if (token) {
verifySession({
tag: 'admin-session',
token
});
}
});
</script>

<template>
<div v-if="isLoading">Checking permissions...</div>

<div v-else-if="!hasAccess">
Access denied. Admin role required.
</div>

<div v-else>
<h1>Admin Dashboard</h1>
<!-- Admin content -->
</div>
</template>

useSessionRefresh

Refresh an expiring session token using a refresh token.

Manual Token Refresh

<script setup lang="ts">
import { useSessionRefresh } from '@ductape/vue';

const { mutate: refreshSession, isLoading } = useSessionRefresh({
onSuccess: (result) => {
localStorage.setItem('token', result.token);
localStorage.setItem('refreshToken', result.refreshToken);
alert('Session refreshed successfully');
},
onError: (error) => {
alert('Failed to refresh session. Please log in again.');
localStorage.removeItem('token');
localStorage.removeItem('refreshToken');
}
});

const handleRefresh = () => {
const refreshToken = localStorage.getItem('refreshToken');
if (refreshToken) {
refreshSession({
tag: 'user-session',
refreshToken
});
}
};
</script>

<template>
<button @click="handleRefresh" :disabled="isLoading">
{{ isLoading ? 'Refreshing...' : 'Refresh Session' }}
</button>
</template>

Conditional Refresh Based on Expiry

<script setup lang="ts">
import { watch } from 'vue';
import { useSessionVerify, useSessionRefresh } from '@ductape/vue';

const { mutate: verifySession, data } = useSessionVerify();
const { mutate: refreshSession } = useSessionRefresh({
onSuccess: (result) => {
localStorage.setItem('token', result.token);
localStorage.setItem('refreshToken', result.refreshToken);
}
});

onMounted(() => {
const token = localStorage.getItem('token');
if (token) {
verifySession({
tag: 'user-session',
token
});
}
});

watch(data, (sessionData) => {
if (!sessionData) return;

const expiresAt = new Date(sessionData.expiresAt).getTime();
const now = Date.now();
const fiveMinutes = 5 * 60 * 1000;

// Refresh if expires in less than 5 minutes
if (expiresAt - now < fiveMinutes) {
const refreshToken = localStorage.getItem('refreshToken');
if (refreshToken) {
refreshSession({
tag: 'user-session',
refreshToken
});
}
}
});
</script>

<template>
<!-- Background component -->
</template>

useSessionAutoRefresh

Automatically refresh tokens before they expire.

Basic Auto-Refresh Setup

<script setup lang="ts">
import { useSessionAutoRefresh } from '@ductape/vue';
import { onMounted } from 'vue';

const { isActive, currentToken, start, stop } = useSessionAutoRefresh({
tag: 'user-session',
refreshToken: localStorage.getItem('refreshToken') || '',
refreshBefore: 5 * 60, // Refresh 5 minutes before expiry
onRefresh: (result) => {
localStorage.setItem('token', result.token);
localStorage.setItem('refreshToken', result.refreshToken);
console.log('Session auto-refreshed');
},
onError: (error) => {
console.error('Auto-refresh failed:', error);
localStorage.removeItem('token');
localStorage.removeItem('refreshToken');
// Redirect to login
}
});

onMounted(() => {
const token = localStorage.getItem('token');
const refreshToken = localStorage.getItem('refreshToken');

if (token && refreshToken) {
start();
}
});
</script>

<template>
<slot />
</template>

Complete Auth Provider

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import {
useSessionStart,
useSessionVerify,
useSessionAutoRefresh
} from '@ductape/vue';

const user = ref<any | null>(null);

const { start: startAutoRefresh, stop: stopAutoRefresh } = useSessionAutoRefresh({
tag: 'user-session',
refreshToken: localStorage.getItem('refreshToken') || '',
onRefresh: (result) => {
localStorage.setItem('token', result.token);
localStorage.setItem('refreshToken', result.refreshToken);
}
});

const { mutate: startSession } = useSessionStart();
const { mutate: verifySession } = useSessionVerify();

onMounted(() => {
// Verify existing session on mount
const token = localStorage.getItem('token');
const refreshToken = localStorage.getItem('refreshToken');

if (token && refreshToken) {
verifySession(
{
tag: 'user-session',
token
},
{
onSuccess: (result) => {
user.value = result.data;
startAutoRefresh();
},
onError: () => {
localStorage.removeItem('token');
localStorage.removeItem('refreshToken');
}
}
);
}
});

const login = (userId: string, email: string) => {
startSession(
{
tag: 'user-session',
data: { userId, email, role: 'user' },
identifier: userId
},
{
onSuccess: (result) => {
localStorage.setItem('token', result.token);
localStorage.setItem('refreshToken', result.refreshToken);
user.value = { userId, email, role: 'user' };
startAutoRefresh();
}
}
);
};

const logout = () => {
stopAutoRefresh();
localStorage.removeItem('token');
localStorage.removeItem('refreshToken');
user.value = null;
};

// Provide these methods to child components via provide/inject if needed
defineExpose({ user, login, logout });
</script>

<template>
<slot :user="user" :login="login" :logout="logout" />
</template>

useSessionRevoke

Revoke a specific session by ID.

Revoke Single Session

<script setup lang="ts">
import { useSessionRevoke } from '@ductape/vue';

defineProps<{ session: any }>();

const { mutate: revokeSession, isLoading } = useSessionRevoke({
onSuccess: () => {
alert('Session revoked successfully');
}
});

const handleRevoke = (sessionId: string) => {
revokeSession({
tag: 'user-session',
sessionId
});
};
</script>

<template>
<div>
<p>Device: {{ session.device }}</p>
<p>Created: {{ new Date(session.createdAt).toLocaleString() }}</p>
<button @click="handleRevoke(session.sessionId)" :disabled="isLoading">
{{ isLoading ? 'Revoking...' : 'Revoke Session' }}
</button>
</div>
</template>

Revoke Current Session (Logout)

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useSessionVerify, useSessionRevoke } from '@ductape/vue';

const { mutate: verifySession, data } = useSessionVerify();
const { mutate: revokeSession, isLoading } = useSessionRevoke({
onSuccess: () => {
localStorage.removeItem('token');
localStorage.removeItem('refreshToken');
window.location.href = '/login';
}
});

onMounted(() => {
const token = localStorage.getItem('token');
if (token) {
verifySession({
tag: 'user-session',
token
});
}
});

const handleLogout = () => {
if (data.value?.sessionId) {
revokeSession({
tag: 'user-session',
sessionId: data.value.sessionId
});
}
};
</script>

<template>
<button @click="handleLogout" :disabled="isLoading">
{{ isLoading ? 'Logging Out...' : 'Log Out' }}
</button>
</template>

useSessionRevokeAll

Revoke all sessions for a user.

Revoke All Sessions

<script setup lang="ts">
import { useSessionRevokeAll } from '@ductape/vue';

defineProps<{ userId: string }>();

const { mutate: revokeAll, isLoading } = useSessionRevokeAll({
onSuccess: (result) => {
alert(`Revoked ${result.revokedCount} sessions`);
}
});

const handleRevokeAll = (userId: string) => {
revokeAll({
tag: 'user-session',
identifier: userId
});
};
</script>

<template>
<button @click="handleRevokeAll(userId)" :disabled="isLoading">
{{ isLoading ? 'Revoking...' : 'Revoke All Sessions' }}
</button>
</template>

Revoke All Except Current

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useSessionVerify, useSessionRevokeAll } from '@ductape/vue';

const { mutate: verifySession, data: sessionData } = useSessionVerify();
const { mutate: revokeAll, isLoading } = useSessionRevokeAll({
onSuccess: (result) => {
alert(`Logged out of ${result.revokedCount} other devices`);
}
});

onMounted(() => {
const token = localStorage.getItem('token');
if (token) {
verifySession({
tag: 'user-session',
token
});
}
});

const handleRevokeOthers = () => {
if (sessionData.value) {
revokeAll({
tag: 'user-session',
identifier: sessionData.value.data.userId,
excludeCurrentSession: sessionData.value.sessionId
});
}
};
</script>

<template>
<div>
<h3>Security</h3>
<button @click="handleRevokeOthers" :disabled="isLoading">
{{ isLoading ? 'Logging Out Other Devices...' : 'Log Out All Other Devices' }}
</button>
</div>
</template>

useSessionList

List active sessions for a user.

List All User Sessions

<script setup lang="ts">
import { onMounted } from 'vue';
import { useSessionList } from '@ductape/vue';

defineProps<{ userId: string }>();

const { mutate: listSessions, data, isLoading } = useSessionList({
onSuccess: (result) => {
console.log(`Found ${result.sessions.length} active sessions`);
}
});

onMounted(() => {
listSessions({
tag: 'user-session',
identifier: props.userId
});
});
</script>

<template>
<div v-if="isLoading">Loading sessions...</div>

<div v-else-if="data">
<h3>Active Sessions ({{ data.sessions.length }})</h3>
<ul>
<li v-for="session in data.sessions" :key="session.sessionId">
<p>Session ID: {{ session.sessionId }}</p>
<p>Created: {{ new Date(session.createdAt).toLocaleString() }}</p>
<p>Expires: {{ new Date(session.expiresAt).toLocaleString() }}</p>
</li>
</ul>
</div>
</template>

Complete Session Manager

<script setup lang="ts">
import { ref, onMounted, watch } from 'vue';
import { useSessionList, useSessionRevoke, useSessionRevokeAll } from '@ductape/vue';

const props = defineProps<{
userId: string;
currentSessionId: string;
}>();

const { mutate: listSessions, data, isLoading: isLoadingList } = useSessionList();

const { mutate: revokeSession } = useSessionRevoke({
onSuccess: () => {
// Refresh the list
listSessions({
tag: 'user-session',
identifier: props.userId
});
}
});

const { mutate: revokeAll } = useSessionRevokeAll({
onSuccess: () => {
listSessions({
tag: 'user-session',
identifier: props.userId
});
}
});

onMounted(() => {
listSessions({
tag: 'user-session',
identifier: props.userId
});
});

watch(() => props.userId, (newUserId) => {
listSessions({
tag: 'user-session',
identifier: newUserId
});
});

const handleRevokeSession = (sessionId: string) => {
if (confirm('Revoke this session?')) {
revokeSession({
tag: 'user-session',
sessionId
});
}
};

const handleRevokeOthers = () => {
if (confirm('Log out all other devices?')) {
revokeAll({
tag: 'user-session',
identifier: props.userId,
excludeCurrentSession: props.currentSessionId
});
}
};
</script>

<template>
<div v-if="isLoadingList">Loading sessions...</div>

<div v-else>
<div style="display: flex; justify-content: space-between; align-items: center">
<h2>Your Sessions</h2>
<button @click="handleRevokeOthers">
Log Out Other Devices
</button>
</div>

<div v-if="data && data.sessions.length > 0">
<div
v-for="session in data.sessions"
:key="session.sessionId"
style="border: 1px solid #ddd; padding: 1rem; margin-bottom: 1rem; border-radius: 4px"
>
<div style="display: flex; justify-content: space-between">
<div>
<p>
<strong>Session ID:</strong> {{ session.sessionId }}
<span
v-if="session.sessionId === currentSessionId"
style="color: green; margin-left: 8px"
>
(Current)
</span>
</p>
<p>Created: {{ new Date(session.createdAt).toLocaleString() }}</p>
<p>Expires: {{ new Date(session.expiresAt).toLocaleString() }}</p>
</div>
<button
v-if="session.sessionId !== currentSessionId"
@click="handleRevokeSession(session.sessionId)"
style="height: fit-content"
>
Revoke
</button>
</div>
</div>
</div>
<p v-else>No active sessions found.</p>
</div>
</template>

Complete Authentication Example

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import {
useSessionStart,
useSessionVerify,
useSessionRevoke,
useSessionAutoRefresh
} from '@ductape/vue';

const user = ref<any | null>(null);

const { start: startAutoRefresh, stop: stopAutoRefresh } = useSessionAutoRefresh({
tag: 'user-session',
refreshToken: localStorage.getItem('refreshToken') || '',
onRefresh: (result) => {
localStorage.setItem('token', result.token);
localStorage.setItem('refreshToken', result.refreshToken);
}
});

const { mutate: startSession, isLoading: isStarting } = useSessionStart({
onSuccess: (result) => {
localStorage.setItem('token', result.token);
localStorage.setItem('refreshToken', result.refreshToken);
startAutoRefresh();
}
});

const { mutate: verifySession } = useSessionVerify({
onSuccess: (result) => {
user.value = result.data;
},
onError: () => {
localStorage.removeItem('token');
localStorage.removeItem('refreshToken');
user.value = null;
}
});

const { mutate: revokeSession } = useSessionRevoke({
onSuccess: () => {
stopAutoRefresh();
localStorage.removeItem('token');
localStorage.removeItem('refreshToken');
user.value = null;
}
});

// Verify session on mount
onMounted(() => {
const token = localStorage.getItem('token');
const refreshToken = localStorage.getItem('refreshToken');

if (token && refreshToken) {
verifySession({
tag: 'user-session',
token
});

startAutoRefresh();
}
});

const handleLogin = () => {
startSession({
tag: 'user-session',
data: {
userId: 'user-123',
email: 'user@example.com',
role: 'user'
},
identifier: 'user-123'
});
};

const handleLogout = () => {
if (user.value?.sessionId) {
revokeSession({
tag: 'user-session',
sessionId: user.value.sessionId
});
}
};
</script>

<template>
<div v-if="!user">
<h1>Please Log In</h1>
<button @click="handleLogin" :disabled="isStarting">
{{ isStarting ? 'Logging In...' : 'Log In' }}
</button>
</div>

<div v-else>
<h1>Welcome, {{ user.email }}!</h1>
<p>User ID: {{ user.userId }}</p>
<p>Role: {{ user.role }}</p>
<button @click="handleLogout">Log Out</button>
</div>
</template>

Type Safety

All session composables are fully typed:

<script setup lang="ts">
import { useSessionStart, useSessionVerify } from '@ductape/vue';

interface UserSessionData {
userId: string;
email: string;
role: 'user' | 'admin';
permissions?: string[];
}

const { mutate: startSession } = useSessionStart({
onSuccess: (result) => {
// TypeScript knows result structure
console.log(result.token);
console.log(result.refreshToken);
console.log(result.sessionId);
}
});

const { mutate: verifySession, data } = useSessionVerify({
onSuccess: (result) => {
// Access encrypted data
const userData = result.data as UserSessionData;
console.log(userData.userId);
console.log(userData.role);
}
});

// Start session with typed data
const handleStart = () => {
startSession({
tag: 'user-session',
data: {
userId: '123',
email: 'user@example.com',
role: 'admin',
permissions: ['users.manage']
} as UserSessionData,
identifier: '123'
});
};
</script>

<template>
<button @click="handleStart">Start Session</button>
</template>

Best Practices

  1. Always Store Both Tokens: Store both token and refreshToken for session continuity
  2. Use Auto-Refresh: Implement useSessionAutoRefresh to avoid session expiration
  3. Verify on Mount: Always verify stored tokens when the app loads
  4. Include Tag Field: All session operations require the tag field to identify session type
  5. Handle Errors: Properly handle verification and refresh errors by clearing invalid tokens
  6. Secure Storage: Consider using secure storage instead of localStorage for sensitive applications
  7. Clean Up: Auto-refresh automatically stops on component unmount
  8. Session Identifiers: Use consistent identifiers (e.g., userId) for session management
  9. Revoke on Logout: Always revoke the session when logging out, not just clearing tokens
  10. Type Your Data: Use TypeScript interfaces for session data for type safety