Authentication
ubean’s first-party auth integration lives in the @ubean/auth package, which wraps Better Auth with graceful fallback to a built-in email/password implementation when better-auth is not installed.
Installation
pnpm add @ubean/auth
# Optional but recommended:
pnpm add better-authConfiguration
Enable the module in ubean.config.ts:
// ubean.config.ts
import { defineConfig } from 'ubean';
export default defineConfig({
auth: true // enable the built-in module with defaults
});For full customization, configure @ubean/auth directly via its Vite plugin:
// vite.config.ts
import { defineConfig } from 'vite';
import { ubeanAuthPlugin } from '@ubean/auth/vite';
export default defineConfig({
plugins: [
ubeanAuthPlugin({
basePath: '/api/auth',
// Better Auth options (forwarded to betterAuth()) —
// object form, or a function: betterAuth: ({ defaults }) => ({ ... })
betterAuth: {
database: { /* Drizzle / Kysely / ... */ },
emailAndPassword: { enabled: true }
},
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!
}
}
})
]
});In development, the plugin mounts the /api/auth/* routes automatically — no route files are needed in dev. For production builds, mount the handler yourself via createAuthHandler() (below) or a custom server integration.
Server-side access
Use createAuthHandler() to mount the auth handler manually (e.g. inside an existing API route or when integrating with a custom server):
// routes/api/auth/[...all].ts
import { defineHandler } from 'ubean/server';
import { createAuthHandler } from '@ubean/auth';
const { handler } = createAuthHandler();
export const ALL = defineHandler(async c => {
return handler(c.req.raw); // handler expects a standard Request
});Read the session inside any API route:
// routes/api/me.ts
import { defineHandler } from 'ubean/server';
import { getServerSession } from '@ubean/auth';
export const GET = defineHandler({
requiresAuth: true
}, async c => {
const session = await getServerSession(c.req.raw);
return c.json({ user: session?.user });
});Server helpers
| Function | Description |
|---|---|
createAuthHandler(opts) | Creates the auth backend; returns { handler, resolveAuth, getOptions } |
authMiddleware() | Hono middleware that attaches user / session to the context |
getUser() | Current user from the request context (no args) |
getSession() | Full { user, session } from the request context |
requireAuth(c?) | Throws a 401 Error if not authenticated; returns the user |
getServerSession(req?) | Server-only session lookup from a Request (or the current context) |
protectRoute(redirectTo = '/login') | Client-side route guard that redirects when unauthenticated |
Client-side usage
useAuth() is a Vue composable that exposes reactive session, user, isAuthenticated, and signIn/signUp/signOut actions:
<script setup lang="ts">
import { useAuth } from '@ubean/auth';
const {
session,
user,
isAuthenticated,
isLoading,
signIn,
signUp,
signOut
} = useAuth();
async function handleLogin(email: string, password: string) {
const { error } = await signIn.email({ email, password, callbackURL: '/dashboard' });
if (error) console.error(error);
}
async function handleGoogle() {
await signIn.social('google', { callbackURL: '/dashboard' });
}
</script>
<template>
<div v-if="isLoading">Checking session…</div>
<div v-else-if="isAuthenticated">Welcome, {{ user?.name }}</div>
<form v-else @submit.prevent="handleLogin(email, password)">…</form>
</template>Protecting routes
Page-level (via definePage)
<!-- pages/dashboard.vue -->
<script setup lang="ts">
definePage({
requiresAuth: true
});
</script>API-level (via defineHandlerMeta)
// routes/api/admin/users.ts
import { defineHandler, defineHandlerMeta } from 'ubean/server';
export const GET = defineHandler(
defineHandlerMeta({ requiresAuth: true }),
async c => {
return c.json({ users: [] });
}
);Programmatic guard
import { defineHandler } from 'ubean/server';
import { requireAuth } from '@ubean/auth';
export const DELETE = defineHandler(async c => {
const session = await requireAuth(c); // throws 401 if missing
return c.json({ ok: true });
});Social providers
Configure OAuth providers in the plugin options:
ubeanAuthPlugin({
socialProviders: {
google: { clientId: '…', clientSecret: '…' },
github: { clientId: '…', clientSecret: '…' }
}
});Trigger the flow from the client:
await signIn.social('github', { callbackURL: '/dashboard' });Middleware pattern (custom JWT)
If you need a custom JWT flow (e.g. token exchange), define a plain ubean middleware. For custom middleware use defineHandler with void-style named exports.
// middleware/auth.ts
import { defineMiddleware } from 'ubean/server';
import { verify } from 'jsonwebtoken';
export default defineMiddleware(async (c, next) => {
const header = c.req.header('Authorization');
if (!header?.startsWith('Bearer ')) {
return c.json({ error: 'Unauthorized' }, 401);
}
try {
c.set('user', verify(header.slice(7), process.env.JWT_SECRET!));
await next();
} catch {
return c.json({ error: 'Invalid token' }, 401);
}
});Mount it via directory convention: middleware/admin/auth.ts applies to /admin/*.
What ubean does NOT provide
defineEventHandler— usedefineHandlerwithGET/POST/… named exports.post.ts/.get.tsfile suffixes — use a singlelogin.tsexporting bothGETandPOSTif needed- Standalone
json()/redirect()helpers — usec.json()/c.redirect()on the Hono context
Best Practices
- Prefer
@ubean/authover hand-rolled auth — Better Auth handles session rotation, CSRF, and OAuth edge cases. - Always set
requiresAuth: trueon protected routes rather than guarding inside the handler body. - Use
getServerSession(c.req.raw)for SSR data loading — it reads cookies directly without an extra round trip. - Rotate secrets — store
JWT_SECRET/ provider secrets indefineEnv()with{ type: String, required: true }. - Combine with
defineRateLimitfor login endpoints to mitigate brute force.