@sp-uvb/nuxt
v0.1.0
Published
Nuxt module for Universal Verification Broker (UVB) authentication
Maintainers
Readme
@sp-uvb/nuxt
Nuxt 3 module for Universal Verification Broker (UVB) authentication. Provides automatic server middleware, composables for client-side access, and utilities for API routes.
Installation
npm install @sp-uvb/nuxt
# or
yarn add @sp-uvb/nuxt
# or
pnpm add @sp-uvb/nuxtQuick Start
1. Add Module to nuxt.config.ts
export default defineNuxtConfig({
modules: ['@sp-uvb/nuxt'],
uvb: {
tenantId: 'my-tenant',
uvbUrl: 'http://localhost:8080', // optional
cookieName: 'uvb_session', // optional
excludePaths: ['/login', '/api/auth'], // optional
autoAuth: true, // optional, default: true
},
});2. Use in Pages
<script setup lang="ts">
const { session, isAuthenticated } = useUvbAuth();
</script>
<template>
<div>
<div v-if="isAuthenticated">
<h1>Welcome {{ session?.userId }}</h1>
<p>Factors: {{ session?.factorsVerified.join(', ') }}</p>
</div>
<div v-else>
<a href="/login">Login</a>
</div>
</div>
</template>3. Use in API Routes
// server/api/profile.ts
export default defineEventHandler((event) => {
const session = requireUvbSession(event);
return {
userId: session.userId,
factors: session.factorsVerified,
};
});API
Module Options
Configure in nuxt.config.ts:
export default defineNuxtConfig({
uvb: {
// Required: Your UVB tenant ID
tenantId: string
// Optional: UVB server URL (default: 'http://localhost:8080')
uvbUrl?: string
// Optional: API key for server-to-server auth
apiKey?: string
// Optional: Cookie name for session token (default: 'uvb_session')
cookieName?: string
// Optional: Paths to exclude from authentication (default: [])
excludePaths?: string[]
// Optional: Enable automatic auth on all routes (default: true)
autoAuth?: boolean
}
})Composables
useUvbSession()
Get the current UVB session.
const session = useUvbSession();
// session.value contains:
// {
// userId: string
// tenantId: string
// sessionId: string
// factorsVerified: string[]
// expiresAt: Date
// }useUvbAuth()
Get authentication state and helpers.
const {
session, // Ref<UvbSession | null>
isAuthenticated, // ComputedRef<boolean>
hasFactor, // (factor: string) => boolean
hasAllFactors, // (factors: string[]) => boolean
hasAnyFactor, // (factors: string[]) => boolean
} = useUvbAuth();Server Utilities
getUvbSession(event)
Get session from event context (returns undefined if no session).
export default defineEventHandler((event) => {
const session = getUvbSession(event);
if (!session) {
return { error: 'Not authenticated' };
}
return { userId: session.userId };
});requireUvbSession(event)
Require session or throw 401 error.
export default defineEventHandler((event) => {
const session = requireUvbSession(event);
// If we get here, user is authenticated
return { userId: session.userId };
});requireFactors(event, factors)
Require specific MFA factors or throw 403 error.
export default defineEventHandler((event) => {
const session = requireFactors(event, ['totp', 'webauthn']);
// User has both TOTP and WebAuthn verified
return { message: 'Admin action completed' };
});hasFactor(event, factor)
Check if user has specific factor.
export default defineEventHandler((event) => {
const hasTotp = hasFactor(event, 'totp');
return { hasTotp };
});isOwner(event, resourceUserId)
Check if current user owns a resource.
export default defineEventHandler(async (event) => {
const post = await getPost(event.context.params.id);
if (!isOwner(event, post.userId)) {
throw createError({ statusCode: 403, message: 'Not your post' });
}
return post;
});requireOwnership(event, resourceUserId)
Require ownership or throw 403 error.
export default defineEventHandler(async (event) => {
const post = await getPost(event.context.params.id);
requireOwnership(event, post.userId);
// Delete post
await deletePost(post.id);
return { success: true };
});Examples
Basic Authentication Page
<!-- pages/profile.vue -->
<script setup lang="ts">
const { session, isAuthenticated } = useUvbAuth();
const router = useRouter();
// Redirect to login if not authenticated
onMounted(() => {
if (!isAuthenticated.value) {
router.push('/login');
}
});
</script>
<template>
<div v-if="isAuthenticated">
<h1>Profile</h1>
<p>User ID: {{ session?.userId }}</p>
<p>Session ID: {{ session?.sessionId }}</p>
<p>Verified Factors: {{ session?.factorsVerified.join(', ') }}</p>
</div>
</template>Conditional UI Based on Factors
<!-- pages/admin.vue -->
<script setup lang="ts">
const { session, hasAllFactors } = useUvbAuth();
const hasAdminAuth = computed(() => hasAllFactors(['totp', 'webauthn']));
</script>
<template>
<div>
<div v-if="hasAdminAuth">
<h1>Admin Panel</h1>
<button @click="performAdminAction">Delete All Data</button>
</div>
<div v-else>
<p>Additional authentication required</p>
<a href="/mfa/setup">Setup MFA</a>
</div>
</div>
</template>Protected API Route
// server/api/admin/users.ts
export default defineEventHandler(async (event) => {
// Require authentication
const session = requireUvbSession(event);
// Require strong MFA
requireFactors(event, ['totp', 'webauthn']);
// Fetch users
const users = await fetchUsers();
return users;
});API Route with Optional Auth
// server/api/posts/[id].ts
export default defineEventHandler(async (event) => {
const postId = event.context.params.id;
const post = await getPost(postId);
// Optional: Show extra data if authenticated
const session = getUvbSession(event);
if (session) {
post.isOwner = post.userId === session.userId;
}
return post;
});Conditional MFA Requirements
// server/api/transfer.post.ts
export default defineEventHandler(async (event) => {
const session = requireUvbSession(event);
const body = await readBody(event);
// Require strong auth for large transfers
if (body.amount > 10000) {
requireFactors(event, ['totp', 'webauthn']);
} else if (body.amount > 1000) {
requireFactors(event, ['totp']);
}
// Process transfer
return { success: true };
});Resource Ownership Check
// server/api/posts/[id].delete.ts
export default defineEventHandler(async (event) => {
const session = requireUvbSession(event);
const postId = event.context.params.id;
const post = await getPost(postId);
// Ensure user owns the post
requireOwnership(event, post.userId);
await deletePost(postId);
return { success: true };
});Using Environment Variables
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@sp-uvb/nuxt'],
uvb: {
tenantId: process.env.UVB_TENANT_ID!,
uvbUrl: process.env.UVB_URL || 'http://localhost:8080',
apiKey: process.env.UVB_API_KEY,
excludePaths: ['/login', '/register', '/api/auth'],
},
});Middleware for Protected Routes
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
const { isAuthenticated } = useUvbAuth();
if (!isAuthenticated.value && to.path !== '/login') {
return navigateTo('/login');
}
});Then use in pages:
<!-- pages/dashboard.vue -->
<script setup lang="ts">
definePageMeta({
middleware: 'auth',
});
</script>
<template>
<div>Protected Dashboard</div>
</template>Server Middleware for Specific Routes
// server/middleware/admin-auth.ts
export default defineEventHandler((event) => {
// Only apply to /api/admin/* routes
if (!event.path.startsWith('/api/admin')) {
return;
}
const session = requireUvbSession(event);
requireFactors(event, ['totp', 'webauthn']);
});Fetch with Session Token
// composables/useApi.ts
export function useApi() {
const config = useRuntimeConfig();
const fetch = async (url: string, options = {}) => {
const token = useCookie(config.public.uvb.cookieName || 'uvb_session');
return $fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${token.value}`,
},
});
};
return { fetch };
}Logout Function
// composables/useAuth.ts
export function useAuth() {
const config = useRuntimeConfig();
const session = useUvbSession();
const logout = async () => {
// Clear session cookie
const cookie = useCookie(config.public.uvb.cookieName || 'uvb_session');
cookie.value = null;
// Clear local session state
session.value = null;
// Redirect to login
await navigateTo('/login');
};
return { logout };
}Disabling Auto Auth
// nuxt.config.ts - Disable automatic authentication
export default defineNuxtConfig({
uvb: {
tenantId: 'my-tenant',
autoAuth: false, // Disable automatic middleware
},
});Then manually protect routes:
// server/api/protected.ts
import { getUvbSession } from '#imports';
export default defineEventHandler((event) => {
// Manually validate session
const session = getUvbSession(event);
if (!session) {
throw createError({ statusCode: 401, message: 'Unauthorized' });
}
return { data: 'protected' };
});TypeScript
This module includes full TypeScript definitions:
import type { UvbSession } from '@sp-uvb/nuxt';
// Session type
interface UvbSession {
userId: string;
tenantId: string;
sessionId: string;
factorsVerified: string[];
expiresAt: Date;
}License
MIT
