Skip to content

Commit 79335b4

Browse files
committed
add Nuxt × Better Auth combined logo in header
1 parent c6ae524 commit 79335b4

15 files changed

Lines changed: 1679 additions & 603 deletions

File tree

README.md

Lines changed: 4 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
<p align="center">
22
<img src="https://raw.githubusercontent.com/onmax/nuxt-better-auth/main/.github/og.png" alt="Nuxt Better Auth" width="100%">
33
<br>
4-
<sub>Design inspired by <a href="https://github.com/HugoRCD">HugoRCD</a>'s work</sub>
4+
<sub>Designed by <a href="https://github.com/HugoRCD">HugoRCD</a></sub>
55
</p>
66

77
<h1 align="center">@onmax/nuxt-better-auth</h1>
88

9-
<p align="center">Nuxt module for <a href="https://better-auth.com">Better Auth</a> with auto schema generation, route protection, and session management.</p>
9+
<p align="center">Nuxt module for <a href="https://better-auth.com">Better Auth</a></p>
1010

1111
<p align="center">
1212
<a href="https://npmjs.com/package/@onmax/nuxt-better-auth"><img src="https://img.shields.io/npm/v/@onmax/nuxt-better-auth/latest.svg?style=flat&colorA=020420&colorB=00DC82" alt="npm version"></a>
@@ -18,162 +18,9 @@
1818
> [!WARNING]
1919
> This library is a work in progress and not ready for production use.
2020
21-
Works with [NuxtHub](https://hub.nuxt.com) and future `@nuxt/db`.
21+
## Documentation
2222

23-
## Features
24-
25-
- **Auto Schema Generation** - Generates Drizzle schema from your better-auth plugins
26-
- **Route Protection** - Declarative access rules via `routeRules`
27-
- **Session Management** - SSR-safe session handling with client hydration
28-
- **Role-Based Access** - Support for `admin`, `user`, and custom roles
29-
- **Auto-Imports** - `useUserSession`, `requireUserSession`, `getUserSession`
30-
- **BetterAuthState Component** - Ready-to-use Vue component with slots
31-
32-
## Requirements
33-
34-
- NuxtHub with database enabled (`hub: { database: true }`) or future `@nuxt/db`
35-
36-
## Quick Start
37-
38-
### 1. Install
39-
40-
```bash
41-
pnpm add @onmax/nuxt-better-auth better-auth drizzle-orm @nuxthub/core
42-
```
43-
44-
### 2. Configure Nuxt
45-
46-
```ts
47-
export default defineNuxtConfig({
48-
modules: ['@nuxthub/core', '@onmax/nuxt-better-auth'],
49-
50-
hub: { database: true },
51-
52-
runtimeConfig: {
53-
betterAuthSecret: '', // BETTER_AUTH_SECRET env var
54-
public: { siteUrl: 'http://localhost:3000' },
55-
},
56-
57-
routeRules: {
58-
'/app/**': { auth: 'user' },
59-
'/admin/**': { auth: { role: 'admin' } },
60-
'/login': { auth: 'guest' },
61-
},
62-
})
63-
```
64-
65-
### 3. Create Server Config
66-
67-
Create `server/auth.config.ts`:
68-
69-
```ts
70-
import { admin } from 'better-auth/plugins'
71-
import { defineServerAuth } from '@onmax/nuxt-better-auth'
72-
73-
export default defineServerAuth(({ db }) => ({
74-
appName: 'My App',
75-
plugins: [admin()],
76-
emailAndPassword: { enabled: true },
77-
}))
78-
```
79-
80-
> Schema is auto-generated from your plugins! No manual Drizzle schema needed.
81-
82-
### 4. Create Client Config
83-
84-
Create `app/auth.client.ts`:
85-
86-
```ts
87-
import { adminClient } from 'better-auth/client/plugins'
88-
import { createAuthClient } from 'better-auth/vue'
89-
90-
export function createAppAuthClient(baseURL: string) {
91-
return createAuthClient({
92-
baseURL,
93-
plugins: [adminClient()],
94-
})
95-
}
96-
97-
export type AppAuthClient = ReturnType<typeof createAppAuthClient>
98-
```
99-
100-
### 5. Add Type Extensions
101-
102-
Create `shared/types/auth.d.ts`:
103-
104-
```ts
105-
import '#nuxt-better-auth'
106-
107-
declare module '#nuxt-better-auth' {
108-
interface AuthUser {
109-
role?: string | null
110-
banned?: boolean | null
111-
}
112-
}
113-
```
114-
115-
## Route Rules
116-
117-
| Option | Type | Description |
118-
|--------|------|-------------|
119-
| `auth` | `false \| 'guest' \| 'user' \| { role: string }` | Auth requirement |
120-
121-
Examples:
122-
- `auth: false` - Public (default)
123-
- `auth: 'guest'` - Only unauthenticated users
124-
- `auth: 'user'` - Any authenticated user
125-
- `auth: { role: 'admin' }` - Requires specific role
126-
127-
## Composables
128-
129-
### `useUserSession()`
130-
131-
```ts
132-
const { user, session, loggedIn, ready, client } = useUserSession()
133-
```
134-
135-
### `<BetterAuthState>`
136-
137-
```vue
138-
<BetterAuthState>
139-
<template #default="{ loggedIn, user, signOut }">
140-
<p v-if="loggedIn">Welcome, {{ user?.name }}</p>
141-
<p v-else>Not logged in</p>
142-
</template>
143-
<template #placeholder>
144-
<p>Loading...</p>
145-
</template>
146-
</BetterAuthState>
147-
```
148-
149-
## Server Utils
150-
151-
```ts
152-
// Require auth
153-
const { user, session } = await requireUserSession(event)
154-
155-
// Require admin
156-
const { user } = await requireUserSession(event, { role: 'admin' })
157-
158-
// Optional session
159-
const session = await getUserSession(event)
160-
```
161-
162-
## Module Aliases
163-
164-
| Alias | Points To |
165-
|-------|-----------|
166-
| `#auth/server` | `server/auth.config.ts` |
167-
| `#auth/client` | `app/auth.client.ts` |
168-
| `#nuxt-better-auth` | Module type augmentation |
169-
170-
## Development
171-
172-
```bash
173-
pnpm install
174-
pnpm dev:prepare
175-
pnpm dev
176-
```
23+
**[nuxt-better-auth.onmax.me](https://nuxt-better-auth.onmax.me/)**
17724

17825
## License
17926

playground/app/auth.client.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import { passkeyClient } from '@better-auth/passkey/client'
12
import { adminClient } from 'better-auth/client/plugins'
23
import { createAuthClient } from 'better-auth/vue'
34

45
export function createAppAuthClient(baseURL: string) {
56
return createAuthClient({
67
baseURL,
7-
plugins: [adminClient()],
8+
plugins: [adminClient(), passkeyClient()],
89
})
910
}
1011

playground/app/layouts/auth.vue

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
<script setup lang="ts">
2+
import type { TabsItem } from '@nuxt/ui'
3+
4+
const route = useRoute()
5+
6+
const tabs: TabsItem[] = [
7+
{ label: 'Sign In', value: 'sign-in' },
8+
{ label: 'Sign Up', value: 'sign-up' },
9+
]
10+
11+
const activeTab = computed({
12+
get: () => route.path === '/register' ? 'sign-up' : 'sign-in',
13+
set: value => navigateTo(value === 'sign-up' ? '/register' : '/login'),
14+
})
15+
16+
const showTabs = computed(() => ['/login', '/register'].includes(route.path))
17+
</script>
18+
119
<template>
220
<div class="min-h-screen w-full dark:bg-black bg-white relative flex justify-center">
321
<!-- Grid background -->
@@ -7,11 +25,23 @@
725
<!-- Header -->
826
<div class="bg-white dark:bg-black border-b py-2 flex justify-between items-center border-border absolute z-50 w-full px-4">
927
<NuxtLink to="/">
10-
<div class="flex items-center gap-2">
11-
<svg width="60" height="45" viewBox="0 0 60 45" fill="none" class="w-5 h-5" xmlns="http://www.w3.org/2000/svg">
12-
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H15V15H30V30H15V45H0V30V15V0ZM45 30V15H30V0H45H60V15V30V45H45H30V30H45Z" class="fill-black dark:fill-white" />
13-
</svg>
14-
<p class="select-none font-medium">BETTER-AUTH.</p>
28+
<div class="flex items-center gap-3">
29+
<!-- Nuxt -->
30+
<div class="flex items-center gap-1.5">
31+
<svg width="48" height="32" viewBox="0 0 48 32" fill="none" class="h-4 w-auto" xmlns="http://www.w3.org/2000/svg">
32+
<path d="M26.88 32H44.64C45.2068 32.0001 45.7492 31.8009 46.24 31.52C46.7308 31.2391 47.2367 30.8865 47.52 30.4C47.8033 29.9135 48.0002 29.3615 48 28.7998C47.9998 28.2381 47.8037 27.6864 47.52 27.2001L35.52 6.56C35.2368 6.0736 34.8907 5.72084 34.4 5.44C33.9093 5.15916 33.2066 4.96 32.64 4.96C32.0734 4.96 31.5307 5.15916 31.04 5.44C30.5493 5.72084 30.2032 6.0736 29.92 6.56L26.88 11.84L20.8 1.59962C20.5165 1.11326 20.1708 0.600786 19.68 0.32C19.1892 0.0392139 18.6467 0 18.08 0C17.5133 0 16.9708 0.0392139 16.48 0.32C15.9892 0.600786 15.4835 1.11326 15.2 1.59962L0.32 27.2001C0.0363166 27.6864 0.000246899 28.2381 3.05588e-07 28.7998C-0.000246288 29.3615 0.0367437 29.9134 0.32 30.3999C0.603256 30.8864 1.10919 31.2391 1.6 31.52C2.09081 31.8009 2.63324 32.0001 3.2 32H14.4C18.8379 32 22.068 30.0092 24.32 26.24L29.76 16.8L32.64 11.84L41.44 26.88H29.76L26.88 32ZM14.24 26.88H6.4L18.08 6.72L24 16.8L20.0786 23.636C18.5831 26.0816 16.878 26.88 14.24 26.88Z" class="fill-black dark:fill-white" />
33+
</svg>
34+
<span class="font-semibold text-sm select-none">Nuxt</span>
35+
</div>
36+
<!-- X separator -->
37+
<span class="text-black/40 dark:text-white/40 text-sm select-none">×</span>
38+
<!-- Better Auth -->
39+
<div class="flex items-center gap-1.5">
40+
<svg width="60" height="45" viewBox="0 0 60 45" fill="none" class="h-4 w-auto" xmlns="http://www.w3.org/2000/svg">
41+
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H15V15H30V30H15V45H0V30V15V0ZM45 30V15H30V0H45H60V15V30V45H45H30V30H45Z" class="fill-black dark:fill-white" />
42+
</svg>
43+
<span class="font-semibold text-sm select-none">Better Auth</span>
44+
</div>
1545
</div>
1646
</NuxtLink>
1747
<div class="z-50 flex items-center">
@@ -21,7 +51,25 @@
2151

2252
<!-- Content -->
2353
<div class="mt-20 lg:w-7/12 w-full relative z-10">
24-
<slot />
54+
<div class="w-full">
55+
<div class="flex items-center flex-col justify-center w-full md:py-10">
56+
<div class="md:w-[400px]">
57+
<UTabs
58+
v-if="showTabs"
59+
v-model="activeTab"
60+
:items="tabs"
61+
:content="false"
62+
:ui="{
63+
root: 'flex flex-col gap-0 items-start',
64+
list: 'p-0 rounded-none bg-transparent border-x border-t max-w-max',
65+
trigger: 'justify-start rounded-none px-4 py-2 text-black dark:text-white data-[state=inactive]:opacity-40 data-[state=active]:opacity-100',
66+
indicator: 'rounded-none inset-y-0 bg-gray-200 dark:bg-zinc-900/90 shadow-none',
67+
}"
68+
/>
69+
<slot />
70+
</div>
71+
</div>
72+
</div>
2573
</div>
2674
</div>
2775
</template>

playground/app/layouts/default.vue

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,35 @@ const { user, loggedIn, ready, signOut } = useUserSession()
77
<header class="border-b border-border">
88
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
99
<div class="flex justify-between items-center h-16">
10-
<NuxtLink to="/" class="text-xl font-semibold text-foreground">
11-
BETTER-AUTH.
10+
<NuxtLink to="/" class="flex items-center gap-3">
11+
<!-- Nuxt -->
12+
<div class="flex items-center gap-1.5">
13+
<svg width="48" height="32" viewBox="0 0 48 32" fill="none" class="h-4 w-auto" xmlns="http://www.w3.org/2000/svg">
14+
<path d="M26.88 32H44.64C45.2068 32.0001 45.7492 31.8009 46.24 31.52C46.7308 31.2391 47.2367 30.8865 47.52 30.4C47.8033 29.9135 48.0002 29.3615 48 28.7998C47.9998 28.2381 47.8037 27.6864 47.52 27.2001L35.52 6.56C35.2368 6.0736 34.8907 5.72084 34.4 5.44C33.9093 5.15916 33.2066 4.96 32.64 4.96C32.0734 4.96 31.5307 5.15916 31.04 5.44C30.5493 5.72084 30.2032 6.0736 29.92 6.56L26.88 11.84L20.8 1.59962C20.5165 1.11326 20.1708 0.600786 19.68 0.32C19.1892 0.0392139 18.6467 0 18.08 0C17.5133 0 16.9708 0.0392139 16.48 0.32C15.9892 0.600786 15.4835 1.11326 15.2 1.59962L0.32 27.2001C0.0363166 27.6864 0.000246899 28.2381 3.05588e-07 28.7998C-0.000246288 29.3615 0.0367437 29.9134 0.32 30.3999C0.603256 30.8864 1.10919 31.2391 1.6 31.52C2.09081 31.8009 2.63324 32.0001 3.2 32H14.4C18.8379 32 22.068 30.0092 24.32 26.24L29.76 16.8L32.64 11.84L41.44 26.88H29.76L26.88 32ZM14.24 26.88H6.4L18.08 6.72L24 16.8L20.0786 23.636C18.5831 26.0816 16.878 26.88 14.24 26.88Z" class="fill-black dark:fill-white" />
15+
</svg>
16+
<span class="font-semibold text-sm select-none">Nuxt</span>
17+
</div>
18+
<!-- X separator -->
19+
<span class="text-black/40 dark:text-white/40 text-sm select-none">×</span>
20+
<!-- Better Auth -->
21+
<div class="flex items-center gap-1.5">
22+
<svg width="60" height="45" viewBox="0 0 60 45" fill="none" class="h-4 w-auto" xmlns="http://www.w3.org/2000/svg">
23+
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H15V15H30V30H15V45H0V30V15V0ZM45 30V15H30V0H45H60V15V30V45H45H30V30H45Z" class="fill-black dark:fill-white" />
24+
</svg>
25+
<span class="font-semibold text-sm select-none">Better Auth</span>
26+
</div>
1227
</NuxtLink>
1328
<div class="flex items-center gap-4">
1429
<nav class="flex items-center gap-4">
15-
<NuxtLink to="/" class="text-sm text-muted-foreground hover:text-foreground">Home</NuxtLink>
16-
<NuxtLink to="/app" class="text-sm text-muted-foreground hover:text-foreground">App</NuxtLink>
17-
<NuxtLink to="/admin" class="text-sm text-muted-foreground hover:text-foreground">Admin</NuxtLink>
30+
<NuxtLink to="/" class="text-sm text-muted-foreground hover:text-foreground">
31+
Home
32+
</NuxtLink>
33+
<NuxtLink to="/app" class="text-sm text-muted-foreground hover:text-foreground">
34+
App
35+
</NuxtLink>
36+
<NuxtLink to="/admin" class="text-sm text-muted-foreground hover:text-foreground">
37+
Admin
38+
</NuxtLink>
1839
</nav>
1940
<UColorModeButton />
2041
<template v-if="ready">

playground/app/pages/admin/index.vue

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,14 @@ const { user } = useUserSession()
44

55
<template>
66
<div class="max-w-2xl mx-auto py-12 px-4">
7-
<h2 class="text-2xl font-semibold mb-4">Admin Dashboard</h2>
8-
<p class="text-muted-foreground mb-2">Welcome, admin {{ user?.name }}!</p>
9-
<p class="text-muted-foreground">This page is only accessible to admins.</p>
7+
<h2 class="text-2xl font-semibold mb-4">
8+
Admin Dashboard
9+
</h2>
10+
<p class="text-muted-foreground mb-2">
11+
Welcome, admin {{ user?.name }}!
12+
</p>
13+
<p class="text-muted-foreground">
14+
This page is only accessible to admins.
15+
</p>
1016
</div>
1117
</template>

playground/app/pages/app/index.vue

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,33 @@
11
<script setup lang="ts">
2-
const { user } = useUserSession()
2+
const { user, signOut } = useUserSession()
3+
const loading = ref(false)
4+
5+
async function handleSignOut() {
6+
loading.value = true
7+
try {
8+
await signOut()
9+
await navigateTo('/login')
10+
}
11+
finally {
12+
loading.value = false
13+
}
14+
}
315
</script>
416

517
<template>
618
<div class="max-w-2xl mx-auto py-12 px-4">
7-
<h2 class="text-2xl font-semibold mb-4">Protected App Page</h2>
8-
<p class="text-muted-foreground mb-2">Welcome to the protected area, {{ user?.name }}!</p>
9-
<p class="text-muted-foreground">This page requires authentication.</p>
19+
<h2 class="text-2xl font-semibold mb-4">
20+
Protected App Page
21+
</h2>
22+
<p class="text-muted-foreground mb-2">
23+
Welcome to the protected area, {{ user?.name }}!
24+
</p>
25+
<p class="text-muted-foreground mb-4">
26+
This page requires authentication.
27+
</p>
28+
<UButton variant="outline" :loading="loading" @click="handleSignOut">
29+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" /><polyline points="16 17 21 12 16 7" /><line x1="21" x2="9" y1="12" y2="12" /></svg>
30+
Sign Out
31+
</UButton>
1032
</div>
1133
</template>

0 commit comments

Comments
 (0)