Skip to content

@tenxyte/vue — Integration Guide

Vue 3 composables for the Tenxyte SDK. Reactive state updates automatically when authentication state changes.


Installation

npm install @tenxyte/core @tenxyte/vue

Requirements: Vue 3.3+, @tenxyte/core ^0.10.0.


Setup

1. Install the plugin

// main.ts
import { createApp } from 'vue';
import { TenxyteClient, LocalStorageAdapter } from '@tenxyte/core';
import { tenxytePlugin } from '@tenxyte/vue';
import App from './App.vue';

const tx = new TenxyteClient({
    baseUrl: 'https://api.my-backend.com',
    accessKey: 'pkg_abc123',
    storage: new LocalStorageAdapter(),
    // cookieMode: true, // Enable if backend uses HttpOnly refresh tokens
});

const app = createApp(App);
app.use(tenxytePlugin, tx);
app.mount('#app');

2. Use composables in any component

<script setup lang="ts">
import { useAuth, useUser, useRbac, useOrganization } from '@tenxyte/vue';

const { isAuthenticated, loading, logout } = useAuth();
const { user } = useUser();
const { hasRole } = useRbac();
</script>

<template>
    <p v-if="loading">Loading...</p>

    <div v-else-if="isAuthenticated">
        <p>Welcome, {{ user?.email }}</p>
        <AdminPanel v-if="hasRole('admin')" />
        <button @click="logout">Sign out</button>
    </div>

    <LoginPage v-else />
</template>

Composables

useAuth()

Reactive authentication state and actions.

const {
    isAuthenticated, // Readonly<Ref<boolean>> — true if access token is valid
    loading,         // Readonly<Ref<boolean>> — true during initial load
    accessToken,     // Readonly<Ref<string | null>> — raw JWT token
    loginWithEmail,  // (data: { email, password, device_info?, totp_code? }) => Promise<void>
    loginWithPhone,  // (data: { phone_country_code, phone_number, password, device_info? }) => Promise<void>
    logout,          // () => Promise<void>
    register,        // (data) => Promise<void>
} = useAuth();

Example — Login form:

<script setup lang="ts">
import { ref } from 'vue';
import { useAuth } from '@tenxyte/vue';

const { isAuthenticated, loginWithEmail, logout, loading } = useAuth();
const email = ref('');
const password = ref('');
const error = ref('');

async function handleLogin() {
    error.value = '';
    try {
        await loginWithEmail({ email: email.value, password: password.value });
    } catch (err: any) {
        error.value = err.error || 'Login failed';
    }
}
</script>

<template>
    <p v-if="loading">Loading...</p>
    <button v-else-if="isAuthenticated" @click="logout">Sign out</button>
    <form v-else @submit.prevent="handleLogin">
        <p v-if="error" style="color: red">{{ error }}</p>
        <input v-model="email" type="email" placeholder="Email" required />
        <input v-model="password" type="password" placeholder="Password" required />
        <button type="submit">Sign in</button>
    </form>
</template>

Example — Registration:

<script setup lang="ts">
import { ref } from 'vue';
import { useAuth } from '@tenxyte/vue';

const { register } = useAuth();
const form = ref({
    email: '',
    password: '',
    first_name: '',
    last_name: '',
});

async function handleRegister() {
    await register(form.value);
}
</script>

<template>
    <form @submit.prevent="handleRegister">
        <input v-model="form.email" type="email" placeholder="Email" required />
        <input v-model="form.password" type="password" placeholder="Password" required />
        <input v-model="form.first_name" placeholder="First name" />
        <input v-model="form.last_name" placeholder="Last name" />
        <button type="submit">Create account</button>
    </form>
</template>

useUser()

Decoded JWT user and profile management.

const {
    user,          // Readonly<Ref<DecodedTenxyteToken | null>> — decoded JWT payload
    loading,       // Readonly<Ref<boolean>>
    getProfile,    // () => Promise<UserProfile> — full profile from API
    updateProfile, // (data) => Promise<unknown>
} = useUser();

Example:

<script setup lang="ts">
import { useUser } from '@tenxyte/vue';
const { user, loading } = useUser();
</script>

<template>
    <div v-if="!loading && user">
        <span>{{ user.first_name }} {{ user.last_name }}</span>
        <small>{{ user.email }}</small>
    </div>
</template>

Example — Profile editing:

<script setup lang="ts">
import { ref } from 'vue';
import { useUser } from '@tenxyte/vue';

const { user, updateProfile, getProfile } = useUser();
const firstName = ref(user.value?.first_name ?? '');

async function handleSave() {
    await updateProfile({ first_name: firstName.value });
    await getProfile();
}
</script>

<template>
    <div>
        <input v-model="firstName" placeholder="First name" />
        <button @click="handleSave">Save</button>
    </div>
</template>

useOrganization()

Multi-tenant context for organizations (B2B).

const {
    activeOrg,          // Readonly<Ref<string | null>> — active org slug
    switchOrganization, // (slug: string) => void
    clearOrganization,  // () => void
} = useOrganization();

Example:

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

const props = defineProps<{ orgs: { slug: string; name: string }[] }>();
const { activeOrg, switchOrganization, clearOrganization } = useOrganization();

function handleChange(event: Event) {
    const value = (event.target as HTMLSelectElement).value;
    value ? switchOrganization(value) : clearOrganization();
}
</script>

<template>
    <select :value="activeOrg ?? ''" @change="handleChange">
        <option value="">No organization</option>
        <option v-for="org in orgs" :key="org.slug" :value="org.slug">
            {{ org.name }}
        </option>
    </select>
</template>

useRbac()

Synchronous role and permission checks from the current JWT.

const {
    hasRole,       // (role: string) => boolean
    hasPermission, // (permission: string) => boolean
    hasAnyRole,    // (roles: string[]) => boolean
    hasAllRoles,   // (roles: string[]) => boolean
} = useRbac();

Example — Component guard:

<script setup lang="ts">
import { useRbac } from '@tenxyte/vue';
const { hasRole } = useRbac();
</script>

<template>
    <div v-if="hasRole('admin')">
        <h2>Administration panel</h2>
        <!-- admin content -->
    </div>
    <p v-else>Access denied. Admin role required.</p>
</template>

Example — Conditional rendering:

<script setup lang="ts">
import { useRbac } from '@tenxyte/vue';
const { hasPermission } = useRbac();
</script>

<template>
    <div>
        <button v-if="hasPermission('posts.create')">New post</button>
        <button v-if="hasPermission('posts.delete')">Delete</button>
    </div>
</template>

Direct Client Access

For features not covered by composables (social login, 2FA, AIRS, etc.), access the TenxyteClient directly:

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

const client = useTenxyteClient();

async function handleGoogleLogin() {
    await client.auth.loginWithSocial('google', {
        id_token: 'google-jwt-token',
    });
}

async function handleGitHubCallback(code: string, redirectUri: string, codeVerifier?: string) {
    await client.auth.handleSocialCallback('github', code, redirectUri, codeVerifier);
}
</script>

<template>
    <button @click="handleGoogleLogin">Sign in with Google</button>
</template>

Direct access examples

const client = useTenxyteClient();

// 2FA
const status = await client.security.get2FAStatus();
await client.security.setup2FA();

// Magic Link
await client.auth.requestMagicLink({
    email: 'user@example.com',
    validation_url: 'https://myapp.com/verify',
});

// AIRS
const agents = await client.ai.listAgentTokens();

// GDPR
await client.gdpr.requestAccountDeletion({ reason: 'Test' });

Error Handling

All SDK methods throw a TenxyteError on failure:

<script setup lang="ts">
import { ref } from 'vue';
import { useAuth } from '@tenxyte/vue';
import type { TenxyteError } from '@tenxyte/core';

const { loginWithEmail } = useAuth();
const error = ref<TenxyteError | null>(null);

async function handleLogin(email: string, password: string) {
    try {
        error.value = null;
        await loginWithEmail({ email, password });
    } catch (err) {
        error.value = err as TenxyteError;
    }
}
</script>

<template>
    <div v-if="error" class="error">
        <strong>{{ error.code }}</strong>: {{ error.error }}
        <p v-if="error.retry_after">Retry in {{ error.retry_after }}s</p>
    </div>
</template>

Common error codes: INVALID_CREDENTIALS, ACCOUNT_LOCKED, 2FA_REQUIRED, RATE_LIMITED, MISSING_REFRESH_TOKEN, INVALID_REDIRECT_URI, APP_AUTH_REQUIRED, APP_AUTH_ORIGIN_REQUIRED, APP_AUTH_ORIGIN_DENIED.

Note: useAuth() in Vue does not yet have an isLoading alias (unlike React). Use loading for now.


If the backend is configured with TENXYTE_REFRESH_TOKEN_COOKIE_ENABLED=True:

const tx = new TenxyteClient({
    baseUrl: 'https://api.my-backend.com',
    accessKey: 'pkg_abc123',
    cookieMode: true,
});

Composables work transparently — auto-refresh uses HttpOnly cookies instead of local storage. No component changes required.

See the Core Guide — Cookie Mode for details.


How It Works

The tenxytePlugin provides the TenxyteClient instance via Vue's dependency injection (app.provide). Each composable calls useTenxyteClient() internally to retrieve the client, then subscribes to SDK events (token:stored, token:refreshed, session:expired) via onMounted/onUnmounted lifecycle hooks. Reactive state is exposed as readonly(ref(...)) so templates update automatically.


Full Application Example

<!-- App.vue -->
<script setup lang="ts">
import { ref } from 'vue';
import { useAuth, useUser, useRbac } from '@tenxyte/vue';

const { isAuthenticated, loading, loginWithEmail, logout } = useAuth();
const { user } = useUser();
const { hasRole } = useRbac();

const email = ref('');
const password = ref('');

async function handleLogin() {
    await loginWithEmail({ email: email.value, password: password.value });
}
</script>

<template>
    <div v-if="loading" class="spinner">Loading...</div>

    <form v-else-if="!isAuthenticated" @submit.prevent="handleLogin">
        <input v-model="email" type="email" placeholder="Email" required />
        <input v-model="password" type="password" placeholder="Password" required />
        <button type="submit">Sign in</button>
    </form>

    <div v-else>
        <header>
            <span>Signed in as: {{ user?.email }}</span>
            <button @click="logout">Sign out</button>
        </header>
        <main>
            <h1>Dashboard</h1>
            <section v-if="hasRole('admin')">Admin area</section>
        </main>
    </div>
</template>

See Also